= ({ data, id, selected }) => {
return (
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/index.css b/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/index.css
index 0ca5af8..ce3f65a 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/index.css
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/index.css
@@ -20,27 +20,6 @@
box-shadow: 0 0 0 2px rgba(0, 120, 212, 0.2);
}
-/* Pulse animation for highlighted nodes */
-.custom-node.pulse-border {
- animation: pulse-border 2s ease-in-out infinite;
- border-color: #ff6b6b;
-}
-
-@keyframes pulse-border {
- 0% {
- border-color: #ff6b6b;
- box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.7);
- }
- 50% {
- border-color: #ff8e8e;
- box-shadow: 0 0 0 8px rgba(255, 107, 107, 0);
- }
- 100% {
- border-color: #ff6b6b;
- box-shadow: 0 0 0 0 rgba(255, 107, 107, 0);
- }
-}
-
/* Node content layout */
.node-content {
text-align: center;
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
index 3d8eb1e..6b7ee5e 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
@@ -51,15 +51,9 @@ interface Workflow {
interface WorkflowGraphProps {
workflow: Workflow;
- highlightedState?: string | null;
- highlightedTransition?: string | null;
}
-const WorkflowGraphInner: React.FC = ({
- workflow,
- highlightedState,
- highlightedTransition,
-}) => {
+const WorkflowGraphInner: React.FC = ({ workflow }) => {
const initialNodes = useMemo(() => {
if (!workflow?.states) return [];
@@ -73,7 +67,6 @@ const WorkflowGraphInner: React.FC = ({
type: 'custom',
data: {
label: state.title,
- highlighted: state.id === highlightedState,
stateId: state.id,
isInitial: state.id === workflow.initial_state,
isFinal: !state.transitions?.length,
@@ -84,7 +77,7 @@ const WorkflowGraphInner: React.FC = ({
},
};
});
- }, [workflow, highlightedState]);
+ }, [workflow]);
const initialEdges = useMemo[]>(() => {
if (!workflow?.states || !workflow?.transitions) return [];
@@ -108,7 +101,6 @@ const WorkflowGraphInner: React.FC = ({
data: {
label: transition.title,
transitionId: transition.id,
- highlighted: transition.id === highlightedTransition,
},
markerEnd: {
type: 'arrowclosed',
@@ -119,7 +111,7 @@ const WorkflowGraphInner: React.FC = ({
});
return edges;
- }, [workflow, highlightedTransition]);
+ }, [workflow]);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
@@ -156,15 +148,7 @@ const WorkflowGraphInner: React.FC = ({
>
-
- node.data.highlighted
- ? '#ff6b6b'
- : node.selected
- ? '#0078d4'
- : '#ddd'
- }
- />
+ (node.selected ? '#0078d4' : '#ddd')} />
);
From 266a97bcfbc735717ca58f5bf12995c63c648602 Mon Sep 17 00:00:00 2001
From: Manas Kenge <110519001+Manas-Kenge@users.noreply.github.com>
Date: Thu, 31 Jul 2025 15:01:19 +0530
Subject: [PATCH 29/60] move interfaces, types
---
.../src/components/States/CreateState.tsx | 16 +-
.../src/components/States/State.tsx | 34 +---
.../components/States/Tabs/GroupRolesTab.tsx | 18 +--
.../States/Tabs/PermissionRolesTab.tsx | 19 +--
.../components/States/Tabs/PropertiesTab.tsx | 14 +-
.../components/States/Tabs/TransitionsTab.tsx | 17 +-
.../components/Transitions/Tabs/GuardsTab.tsx | 26 +---
.../Transitions/Tabs/PropertiesTab.tsx | 15 +-
.../Transitions/Tabs/SourceStatesTab.tsx | 17 +-
.../src/components/Transitions/Transition.tsx | 29 +---
.../components/Workflow/CreateWorkflow.tsx | 7 +-
.../components/Workflow/WorkflowSettings.tsx | 10 +-
.../src/components/Workflow/WorkflowTab.tsx | 15 +-
.../src/components/Workflow/WorkflowView.tsx | 10 +-
.../src/constants/index.ts | 2 +-
.../src/reducers/state.ts | 66 +-------
.../src/reducers/transition.ts | 80 +---------
.../src/reducers/workflow.ts | 84 +---------
.../volto-workflow-manager/src/types/index.ts | 13 +-
.../volto-workflow-manager/src/types/state.ts | 146 ++++++++++++++++++
.../src/types/transition.ts | 143 +++++++++++++++++
.../src/types/workflow.ts | 105 +++++++++++++
22 files changed, 439 insertions(+), 447 deletions(-)
create mode 100644 frontend/packages/volto-workflow-manager/src/types/state.ts
create mode 100644 frontend/packages/volto-workflow-manager/src/types/transition.ts
create mode 100644 frontend/packages/volto-workflow-manager/src/types/workflow.ts
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx b/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
index 7b8214a..146d111 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
@@ -17,13 +17,8 @@ import {
} from '@adobe/react-spectrum';
import { addState, getWorkflows } from '../../actions';
import { useAppDispatch, useAppSelector } from '../../types';
-import type { RootState } from '../../types';
-
-interface CreateStateProps {
- workflowId: string;
- isOpen: boolean;
- onClose: () => void;
-}
+import type { GlobalRootState } from '../../types';
+import type { CreateStateProps } from '../../types/state';
const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
const dispatch = useAppDispatch();
@@ -34,12 +29,13 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
const [submitError, setSubmitError] = useState(null);
const [submitSuccess, setSubmitSuccess] = useState(false);
- const currentWorkflow = useAppSelector((state: RootState) =>
+ const currentWorkflow = useAppSelector((state: GlobalRootState) =>
state.workflow.workflows.items.find((wf) => wf.id === workflowId),
);
- // Get add state status from Redux store
- const addStateStatus = useAppSelector((state: RootState) => state.state?.add);
+ const addStateStatus = useAppSelector(
+ (state: GlobalRootState) => state.state?.add,
+ );
// Reset form when dialog opens/closes
useEffect(() => {
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/State.tsx b/frontend/packages/volto-workflow-manager/src/components/States/State.tsx
index 5344a82..b66d61b 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/State.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/State.tsx
@@ -17,38 +17,8 @@ import PropertiesTab from './Tabs/PropertiesTab';
import TransitionsTab from './Tabs/TransitionsTab';
import PermissionRolesTab from './Tabs/PermissionRolesTab';
import GroupRolesTab from './Tabs/GroupRolesTab';
-import type { WorkflowReduxState } from '../../reducers/workflow';
-import type { StateReduxState } from '../../reducers/state';
-import type { TransitionReduxState } from '../../reducers/transition';
-
-export interface StateData {
- properties: {
- title: string;
- description: string;
- isInitialState: boolean;
- };
- transitions: {
- selected: string[];
- };
- permissions: {
- [permissionName: string]: string[];
- };
- groupRoles: {
- [groupId: string]: string[];
- };
-}
-
-interface GlobalRootState {
- workflow: WorkflowReduxState;
- state: StateReduxState;
- transition: TransitionReduxState;
-}
-
-interface StateProps {
- workflowId: string;
- onDataChange: (payload: any | null) => void;
- isDisabled: boolean;
-}
+import type { GlobalRootState } from '../../types';
+import type { StateData, StateProps } from '../../types/state';
const propertiesSchema = {
title: 'State Properties',
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/GroupRolesTab.tsx b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/GroupRolesTab.tsx
index 3ee81aa..a105e75 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/GroupRolesTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/GroupRolesTab.tsx
@@ -11,25 +11,9 @@ import {
Cell,
Checkbox,
} from '@adobe/react-spectrum';
+import type { GroupRolesTabProps } from '../../../types/state';
import { cloneDeep } from 'lodash';
-export interface GroupRolesData {
- [groupId: string]: string[];
-}
-
-interface GroupInfo {
- id: string;
- title: string;
-}
-
-interface GroupRolesTabProps {
- data: GroupRolesData;
- groups: GroupInfo[];
- availableRoles: string[];
- onChange: (newData: GroupRolesData) => void;
- isDisabled: boolean;
-}
-
const GroupRolesTab: React.FC = ({
data,
groups,
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PermissionRolesTab.tsx b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PermissionRolesTab.tsx
index 4bb7dea..8e1664d 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PermissionRolesTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PermissionRolesTab.tsx
@@ -11,26 +11,9 @@ import {
Cell,
Checkbox,
} from '@adobe/react-spectrum';
+import type { PermissionRolesTabProps } from '../../../types/state';
import { cloneDeep } from 'lodash';
-export interface PermissionRolesData {
- [permissionName: string]: string[];
-}
-
-interface PermissionInfo {
- name: string;
- perm: string;
- description: string;
-}
-
-interface PermissionRolesTabProps {
- data: PermissionRolesData;
- managedPermissions: PermissionInfo[];
- availableRoles: string[];
- onChange: (newData: PermissionRolesData) => void;
- isDisabled: boolean;
-}
-
const PermissionRolesTab: React.FC = ({
data,
managedPermissions,
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PropertiesTab.tsx b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PropertiesTab.tsx
index e4a4a42..05310fc 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PropertiesTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/PropertiesTab.tsx
@@ -1,19 +1,7 @@
import React, { useCallback } from 'react';
import { View, Text } from '@adobe/react-spectrum';
import Form from '@plone/volto/components/manage/Form/Form';
-
-export interface PropertiesData {
- isInitialState: boolean;
- title: string;
- description: string;
-}
-
-interface PropertiesTabProps {
- data: PropertiesData;
- schema: any;
- onChange: (newData: PropertiesData) => void;
- isDisabled: boolean;
-}
+import type { PropertiesTabProps } from '../../../types/state';
const PropertiesTab: React.FC = ({
data,
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/TransitionsTab.tsx b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/TransitionsTab.tsx
index 7a99d01..03b4daf 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/Tabs/TransitionsTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/Tabs/TransitionsTab.tsx
@@ -1,21 +1,6 @@
import React from 'react';
import { Checkbox, Flex, View, Text } from '@adobe/react-spectrum';
-
-export interface TransitionsData {
- selected: string[];
-}
-
-interface AvailableTransition {
- id: string;
- title: string;
-}
-
-interface TransitionsTabProps {
- data: TransitionsData;
- availableTransitions: AvailableTransition[];
- onChange: (newData: TransitionsData) => void;
- isDisabled: boolean;
-}
+import type { TransitionsTabProps } from '../../../types/state';
const TransitionsTab: React.FC = ({
data,
diff --git a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/GuardsTab.tsx b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/GuardsTab.tsx
index b9068db..316283c 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/GuardsTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/GuardsTab.tsx
@@ -7,31 +7,7 @@ import {
ListView,
Item,
} from '@adobe/react-spectrum';
-
-export interface GuardsData {
- roles: string[];
- groups: string[];
- permissions: string[];
- expr: string;
-}
-
-interface GroupInfo {
- id: string;
- title: string;
-}
-interface PermissionInfo {
- perm: string;
- name: string;
-}
-
-interface GuardsTabProps {
- data: GuardsData;
- availableRoles: string[];
- availableGroups: GroupInfo[];
- availablePermissions: PermissionInfo[];
- onChange: (newData: GuardsData) => void;
- isDisabled: boolean;
-}
+import type { GuardsTabProps } from '../../../types/transition';
const GuardsTab: React.FC = ({
data,
diff --git a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/PropertiesTab.tsx b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/PropertiesTab.tsx
index 64ec34a..6ef5796 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/PropertiesTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/PropertiesTab.tsx
@@ -1,20 +1,7 @@
import React, { useCallback } from 'react';
import { View, Text } from '@adobe/react-spectrum';
import Form from '@plone/volto/components/manage/Form/Form';
-
-export interface PropertiesData {
- title: string;
- description: string;
- new_state_id: string | null;
- trigger_type: boolean;
-}
-
-interface PropertiesTabProps {
- data: PropertiesData;
- schema: any;
- onChange: (newData: PropertiesData) => void;
- isDisabled: boolean;
-}
+import type { PropertiesTabProps } from '../../../types/transition';
const PropertiesTab: React.FC = ({
data,
diff --git a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/SourceStatesTab.tsx b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/SourceStatesTab.tsx
index 797a3b5..b2f1f0c 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/SourceStatesTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/SourceStatesTab.tsx
@@ -1,21 +1,6 @@
import React from 'react';
import { Checkbox, Flex, View, Text, Heading } from '@adobe/react-spectrum';
-
-export interface SourceStatesData {
- selected: string[];
-}
-
-interface AvailableState {
- id: string;
- title: string;
-}
-
-interface SourceStatesTabProps {
- data: SourceStatesData;
- availableStates: AvailableState[];
- onChange: (newData: SourceStatesData) => void;
- isDisabled: boolean;
-}
+import type { SourceStatesTabProps } from '../../../types/transition';
const SourceStatesTab: React.FC = ({
data,
diff --git a/frontend/packages/volto-workflow-manager/src/components/Transitions/Transition.tsx b/frontend/packages/volto-workflow-manager/src/components/Transitions/Transition.tsx
index 92337d8..eb2f887 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Transitions/Transition.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Transitions/Transition.tsx
@@ -13,31 +13,12 @@ import {
} from '@adobe/react-spectrum';
import { listTransitions } from '../../actions/transition';
import { listStates } from '../../actions/state';
-import PropertiesTab, { type PropertiesData } from './Tabs/PropertiesTab';
-import GuardsTab, { type GuardsData } from './Tabs/GuardsTab';
-import SourceStatesTab, { type SourceStatesData } from './Tabs/SourceStatesTab';
+import PropertiesTab from './Tabs/PropertiesTab';
+import GuardsTab from './Tabs/GuardsTab';
+import SourceStatesTab from './Tabs/SourceStatesTab';
import ActionsTab from './Tabs/ActionsTab';
-import type { WorkflowReduxState } from '../../reducers/workflow';
-import type { StateReduxState } from '../../reducers/state';
-import type { TransitionReduxState } from '../../reducers/transition';
-
-export interface TransitionData {
- properties: PropertiesData;
- guards: GuardsData;
- sourceStates: SourceStatesData;
-}
-
-interface GlobalRootState {
- workflow: WorkflowReduxState;
- state: StateReduxState;
- transition: TransitionReduxState;
-}
-
-interface TransitionProps {
- workflowId: string;
- onDataChange: (payload: any | null) => void;
- isDisabled: boolean;
-}
+import type { GlobalRootState } from '../../types';
+import type { TransitionData, TransitionProps } from '../../types/transition';
const Transition: React.FC = ({
workflowId,
diff --git a/frontend/packages/volto-workflow-manager/src/components/Workflow/CreateWorkflow.tsx b/frontend/packages/volto-workflow-manager/src/components/Workflow/CreateWorkflow.tsx
index 92dfd54..626dd19 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Workflow/CreateWorkflow.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Workflow/CreateWorkflow.tsx
@@ -13,8 +13,13 @@ import {
View,
} from '@adobe/react-spectrum';
import ThemeProvider from '../../Provider';
+import type { CreateWorkflowProps } from '../../types/workflow';
-const CreateWorkflow = ({ workflows, onCreate, close }) => {
+const CreateWorkflow: React.FC = ({
+ workflows,
+ onCreate,
+ close,
+}) => {
const [selectedWorkflow, setSelectedWorkflow] = useState(null);
const [workflowName, setWorkflowName] = useState('');
diff --git a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowSettings.tsx b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowSettings.tsx
index 398ac8c..b624073 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowSettings.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowSettings.tsx
@@ -22,17 +22,9 @@ import { updateTransition } from '../../actions/transition';
import State from '../States/State';
import Transition from '../Transitions/Transition';
import ThemeProvider from '../../Provider';
-import type { WorkflowReduxState } from '../../reducers/workflow';
-import type { StateReduxState } from '../../reducers/state';
-import type { TransitionReduxState } from '../../reducers/transition';
import WorkflowTab from './WorkflowTab';
import WorkflowHeader from './WorkflowHeader';
-
-interface GlobalRootState {
- workflow: WorkflowReduxState;
- state: StateReduxState;
- transition: TransitionReduxState;
-}
+import type { GlobalRootState } from '../../types';
const WorkflowSettings: React.FC = (props) => {
const { workflowId } = useParams<{ workflowId: string }>();
diff --git a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowTab.tsx b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowTab.tsx
index 29ac00e..17967a6 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowTab.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowTab.tsx
@@ -3,19 +3,8 @@ import { useDispatch, useSelector } from 'react-redux';
import { View, ProgressCircle } from '@adobe/react-spectrum';
import Form from '@plone/volto/components/manage/Form/Form';
import { getWorkflow } from '../../actions/workflow';
-import type { WorkflowReduxState } from '../../reducers/workflow';
-
-interface GlobalRootState {
- workflow: WorkflowReduxState;
-}
-
-interface WorkflowTabProps {
- workflowId: string;
- onDataChange: (
- payload: { title: string; description: string } | null,
- ) => void;
- isDisabled: boolean;
-}
+import type { GlobalRootState } from '../../types';
+import type { WorkflowTabProps } from '../../types/workflow';
const workflowSchema = {
title: 'Workflow Properties',
diff --git a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
index 856bfc4..35297f4 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
@@ -20,12 +20,12 @@ import settings from '@plone/volto/icons/settings.svg';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import { createPortal } from 'react-dom';
import { useClient } from '@plone/volto/hooks/client/useClient';
+import type { WorkflowViewProps } from '../../types/workflow';
-interface WorkflowViewProps {
- workflowId: string;
-}
-
-const WorkflowView: React.FC = ({ workflowId }, props) => {
+const WorkflowView: React.FC = ({
+ workflowId,
+ pathname,
+}) => {
const isClient = useClient();
const workflows = useAppSelector((state) => state.workflow.workflows.items);
const workflow = useAppSelector((state) =>
diff --git a/frontend/packages/volto-workflow-manager/src/constants/index.ts b/frontend/packages/volto-workflow-manager/src/constants/index.ts
index 2a648a1..1d3158f 100644
--- a/frontend/packages/volto-workflow-manager/src/constants/index.ts
+++ b/frontend/packages/volto-workflow-manager/src/constants/index.ts
@@ -1,3 +1,4 @@
+export const GET_WORKFLOW = 'GET_WORKFLOW' as const;
export const GET_WORKFLOWS = 'GET_WORKFLOWS' as const;
export const ADD_WORKFLOW = 'ADD_WORKFLOW' as const;
export const DELETE_WORKFLOW = 'DELETE_WORKFLOW' as const;
@@ -7,7 +8,6 @@ export const UPDATE_WORKFLOW_STATE = 'UPDATE_WORKFLOW_STATE';
export const ASSIGN_WORKFLOW = 'ASSIGN_WORKFLOW' as const;
export const VALIDATE_WORKFLOW = 'VALIDATE_WORKFLOW' as const;
export const CLEAR_LAST_CREATED_WORKFLOW = 'CLEAR_LAST_CREATED_WORKFLOW';
-export const GET_WORKFLOW = 'GET_WORKFLOW' as const;
export const LIST_STATES = 'LIST_STATES';
export const ADD_STATE = 'ADD_STATE';
diff --git a/frontend/packages/volto-workflow-manager/src/reducers/state.ts b/frontend/packages/volto-workflow-manager/src/reducers/state.ts
index b992a5b..53fa23f 100644
--- a/frontend/packages/volto-workflow-manager/src/reducers/state.ts
+++ b/frontend/packages/volto-workflow-manager/src/reducers/state.ts
@@ -5,71 +5,7 @@ import {
DELETE_STATE,
LIST_STATES,
} from '../constants';
-
-// Represents a single state object as returned by the backend
-export interface StateObject {
- id: string;
- title: string;
- description: string;
- transitions: string[];
- permission_roles: Record;
- group_roles: Record;
-}
-
-// Response from LIST_STATES endpoint (GET /@states/{workflow_id})
-export interface ListStatesResponse {
- workflow_id: string;
- workflow_title: string;
- initial_state: string | null;
- states: StateObject[];
-}
-
-// Response from ADD_STATE and UPDATE_STATE endpoints
-export interface StateActionResponse {
- status: string;
- state: StateObject;
- message: string;
-}
-
-// Response from DELETE_STATE endpoint
-export interface DeleteStateResponse {
- status: string;
- message: string;
-}
-
-// Define the shape of this reducer's state slice
-export interface StateReduxState {
- get: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: StateObject | null;
- };
- list: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: ListStatesResponse | null;
- };
- add: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: StateActionResponse | null;
- };
- update: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: StateActionResponse | null;
- };
- delete: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: DeleteStateResponse | null;
- };
-}
+import type { StateReduxState } from '../types/state';
const initialState: StateReduxState = {
get: {
diff --git a/frontend/packages/volto-workflow-manager/src/reducers/transition.ts b/frontend/packages/volto-workflow-manager/src/reducers/transition.ts
index 2021b82..a708b7b 100644
--- a/frontend/packages/volto-workflow-manager/src/reducers/transition.ts
+++ b/frontend/packages/volto-workflow-manager/src/reducers/transition.ts
@@ -6,83 +6,7 @@ import {
UPDATE_TRANSITION,
DELETE_TRANSITION,
} from '../constants';
-
-export interface Transition {
- id: string;
- title: string;
- description: string;
- new_state_id: string;
- trigger_type: number;
- guard: {
- permissions: string[];
- roles: string[];
- groups: string[];
- expr: string;
- };
-}
-
-export interface ListTransitionsResponse {
- workflow_id: string;
- workflow_title: string;
- transitions: Transition[];
-}
-
-export interface GetTransitionResponse {
- workflow_id: string;
- transition: Transition;
- states_with_this_transition: string[];
- available_states: { id: string; title: string }[];
- available_transitions: { id: string; title: string }[];
- guard_options: {
- permissions: string[];
- roles: string[];
- groups: { id: string; title: string }[];
- };
-}
-
-export interface TransitionActionResponse {
- status: string;
- transition: Transition;
- message: string;
-}
-
-export interface DeleteTransitionResponse {
- status: string;
- message: string;
-}
-
-export interface TransitionReduxState {
- list: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: ListTransitionsResponse | null;
- };
- get: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: GetTransitionResponse | null;
- };
- add: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: TransitionActionResponse | null;
- };
- update: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: TransitionActionResponse | null;
- };
- delete: {
- loading: boolean;
- loaded: boolean;
- error: string | null;
- data: DeleteTransitionResponse | null;
- };
-}
+import type { TransitionReduxState } from '../types/transition';
const initialState: TransitionReduxState = {
list: { loading: false, loaded: false, error: null, data: null },
@@ -92,8 +16,6 @@ const initialState: TransitionReduxState = {
delete: { loading: false, loaded: false, error: null, data: null },
};
-// --- Reducer Function ---
-
export default function transition(
state: TransitionReduxState = initialState,
action: AnyAction,
diff --git a/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts b/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
index 23bfaf1..8f7ce87 100644
--- a/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
+++ b/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
@@ -1,5 +1,4 @@
import type { AnyAction } from 'redux';
-
import {
GET_WORKFLOWS,
ADD_WORKFLOW,
@@ -11,88 +10,7 @@ import {
VALIDATE_WORKFLOW,
CLEAR_LAST_CREATED_WORKFLOW,
} from '../constants';
-
-export interface PermissionInfo {
- perm: string;
- name: string;
- description: string;
-}
-
-export interface GroupInfo {
- id: string;
- title: string;
-}
-
-export interface ContextData {
- available_roles: string[];
- groups: GroupInfo[];
- managed_permissions: PermissionInfo[];
-}
-
-export interface Workflow {
- id: string;
- title: string;
- description: string;
- assigned_types: string[];
- initial_state: string;
- states: WorkflowState[];
- transitions: WorkflowTransition[];
- context_data?: ContextData;
-}
-
-export interface WorkflowState {
- id: string;
- title: string;
- description?: string;
- isInitial?: boolean;
- isFinal?: boolean;
- permissions?: string[];
- transitions?: string[];
-}
-
-export interface WorkflowTransition {
- id: string;
- title: string;
- new_state: string;
-}
-
-export interface ValidationError {
- id?: string;
- title?: string;
- error: string;
-}
-
-export interface ValidationErrors {
- state_errors: ValidationError[];
- transition_errors: ValidationError[];
- initial_state_error: boolean;
-}
-
-interface WorkflowReduxState {
- workflow: {
- currentWorkflow: Workflow;
- error: string | null;
- loaded: boolean;
- loading: boolean;
- };
- workflows: {
- error: string | null;
- items: Workflow[];
- loaded: boolean;
- loading: boolean;
- };
- validation: {
- error: string | null;
- errors: ValidationErrors | null;
- loading: boolean;
- };
- operation: {
- error: string | null;
- loading: boolean;
- result: any;
- };
- lastCreatedWorkflowId: string | null;
-}
+import type { WorkflowReduxState } from '../types/workflow';
const initialState: WorkflowReduxState = {
workflow: {
diff --git a/frontend/packages/volto-workflow-manager/src/types/index.ts b/frontend/packages/volto-workflow-manager/src/types/index.ts
index 66d27ab..36d63f3 100644
--- a/frontend/packages/volto-workflow-manager/src/types/index.ts
+++ b/frontend/packages/volto-workflow-manager/src/types/index.ts
@@ -3,17 +3,17 @@ import {
useDispatch,
useSelector,
} from 'react-redux';
-import type { WorkflowReduxState } from '../reducers/workflow';
-import type { WorkflowState } from '../reducers/workflow';
-import type { StateReduxState } from '../reducers/state';
-import type { TransitionReduxState } from '../reducers/transition';
+import type { WorkflowReduxState } from './workflow';
+import type { WorkflowState } from './workflow';
+import type { StateReduxState } from './state';
+import type { TransitionReduxState } from './transition';
export type UpdateStatePayload = Partial> & {
is_initial_state?: boolean;
states_with_this_transition?: string[];
};
-export interface RootState {
+export interface GlobalRootState {
workflow: WorkflowReduxState;
state: StateReduxState;
transition: TransitionReduxState;
@@ -22,7 +22,8 @@ export interface RootState {
export type AppDispatch = any;
export const useAppDispatch = () => useDispatch();
-export const useAppSelector: TypedUseSelectorHook = useSelector;
+export const useAppSelector: TypedUseSelectorHook =
+ useSelector;
export enum HistoryAction {
AddNode = 'addNode',
diff --git a/frontend/packages/volto-workflow-manager/src/types/state.ts b/frontend/packages/volto-workflow-manager/src/types/state.ts
new file mode 100644
index 0000000..c4e6bc5
--- /dev/null
+++ b/frontend/packages/volto-workflow-manager/src/types/state.ts
@@ -0,0 +1,146 @@
+export interface GroupRolesData {
+ [groupId: string]: string[];
+}
+
+export interface GroupInfo {
+ id: string;
+ title: string;
+}
+
+export interface GroupRolesTabProps {
+ data: GroupRolesData;
+ groups: GroupInfo[];
+ availableRoles: string[];
+ onChange: (newData: GroupRolesData) => void;
+ isDisabled: boolean;
+}
+
+export interface PermissionRolesData {
+ [permissionName: string]: string[];
+}
+
+export interface PermissionInfo {
+ name: string;
+ perm: string;
+ description: string;
+}
+
+export interface PermissionRolesTabProps {
+ data: PermissionRolesData;
+ managedPermissions: PermissionInfo[];
+ availableRoles: string[];
+ onChange: (newData: PermissionRolesData) => void;
+ isDisabled: boolean;
+}
+
+export interface PropertiesData {
+ isInitialState: boolean;
+ title: string;
+ description: string;
+}
+
+export interface PropertiesTabProps {
+ data: PropertiesData;
+ schema: any;
+ onChange: (newData: PropertiesData) => void;
+ isDisabled: boolean;
+}
+
+export interface TransitionsData {
+ selected: string[];
+}
+
+export interface AvailableTransition {
+ id: string;
+ title: string;
+}
+
+export interface TransitionsTabProps {
+ data: TransitionsData;
+ availableTransitions: AvailableTransition[];
+ onChange: (newData: TransitionsData) => void;
+ isDisabled: boolean;
+}
+
+export interface StateData {
+ properties: PropertiesData;
+ transitions: TransitionsData;
+ permissions: PermissionRolesData;
+ groupRoles: GroupRolesData;
+}
+
+export interface StateProps {
+ workflowId: string;
+ onDataChange: (payload: any | null) => void;
+ isDisabled: boolean;
+}
+
+export interface CreateStateProps {
+ workflowId: string;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export interface StateObject {
+ id: string;
+ title: string;
+ description: string;
+ transitions: string[];
+ permission_roles: Record;
+ group_roles: Record;
+}
+
+// Response from LIST_STATES endpoint (GET /@states/{workflow_id})
+export interface ListStatesResponse {
+ workflow_id: string;
+ workflow_title: string;
+ initial_state: string | null;
+ states: StateObject[];
+}
+
+// Response from ADD_STATE and UPDATE_STATE endpoints
+export interface StateActionResponse {
+ status: string;
+ state: StateObject;
+ message: string;
+}
+
+// Response from DELETE_STATE endpoint
+export interface DeleteStateResponse {
+ status: string;
+ message: string;
+}
+
+// Define the shape of the "state" slice in the Redux store
+export interface StateReduxState {
+ get: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: StateObject | null;
+ };
+ list: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: ListStatesResponse | null;
+ };
+ add: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: StateActionResponse | null;
+ };
+ update: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: StateActionResponse | null;
+ };
+ delete: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: DeleteStateResponse | null;
+ };
+}
diff --git a/frontend/packages/volto-workflow-manager/src/types/transition.ts b/frontend/packages/volto-workflow-manager/src/types/transition.ts
new file mode 100644
index 0000000..1ea707d
--- /dev/null
+++ b/frontend/packages/volto-workflow-manager/src/types/transition.ts
@@ -0,0 +1,143 @@
+export interface GuardsData {
+ roles: string[];
+ groups: string[];
+ permissions: string[];
+ expr: string;
+}
+
+export interface GroupInfo {
+ id: string;
+ title: string;
+}
+export interface PermissionInfo {
+ perm: string;
+ name: string;
+}
+
+export interface GuardsTabProps {
+ data: GuardsData;
+ availableRoles: string[];
+ availableGroups: GroupInfo[];
+ availablePermissions: PermissionInfo[];
+ onChange: (newData: GuardsData) => void;
+ isDisabled: boolean;
+}
+
+export interface PropertiesData {
+ title: string;
+ description: string;
+ new_state_id: string | null;
+ trigger_type: boolean;
+}
+
+export interface PropertiesTabProps {
+ data: PropertiesData;
+ schema: any;
+ onChange: (newData: PropertiesData) => void;
+ isDisabled: boolean;
+}
+
+export interface SourceStatesData {
+ selected: string[];
+}
+
+export interface AvailableState {
+ id: string;
+ title: string;
+}
+
+export interface SourceStatesTabProps {
+ data: SourceStatesData;
+ availableStates: AvailableState[];
+ onChange: (newData: SourceStatesData) => void;
+ isDisabled: boolean;
+}
+
+export interface TransitionData {
+ properties: PropertiesData;
+ guards: GuardsData;
+ sourceStates: SourceStatesData;
+}
+
+export interface TransitionProps {
+ workflowId: string;
+ onDataChange: (payload: any | null) => void;
+ isDisabled: boolean;
+}
+
+export interface Transition {
+ id: string;
+ title: string;
+ description: string;
+ new_state_id: string;
+ trigger_type: number;
+ guard: {
+ permissions: string[];
+ roles: string[];
+ groups: string[];
+ expr: string;
+ };
+}
+
+export interface ListTransitionsResponse {
+ workflow_id: string;
+ workflow_title: string;
+ transitions: Transition[];
+}
+
+export interface GetTransitionResponse {
+ workflow_id: string;
+ transition: Transition;
+ states_with_this_transition: string[];
+ available_states: { id: string; title: string }[];
+ available_transitions: { id: string; title: string }[];
+ guard_options: {
+ permissions: string[];
+ roles: string[];
+ groups: { id: string; title: string }[];
+ };
+}
+
+export interface TransitionActionResponse {
+ status: string;
+ transition: Transition;
+ message: string;
+}
+
+export interface DeleteTransitionResponse {
+ status: string;
+ message: string;
+}
+
+export interface TransitionReduxState {
+ list: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: ListTransitionsResponse | null;
+ };
+ get: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: GetTransitionResponse | null;
+ };
+ add: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: TransitionActionResponse | null;
+ };
+ update: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: TransitionActionResponse | null;
+ };
+ delete: {
+ loading: boolean;
+ loaded: boolean;
+ error: string | null;
+ data: DeleteTransitionResponse | null;
+ };
+}
diff --git a/frontend/packages/volto-workflow-manager/src/types/workflow.ts b/frontend/packages/volto-workflow-manager/src/types/workflow.ts
new file mode 100644
index 0000000..172b07a
--- /dev/null
+++ b/frontend/packages/volto-workflow-manager/src/types/workflow.ts
@@ -0,0 +1,105 @@
+export interface CreateWorkflowProps {
+ workflows: Workflow[];
+ onCreate: (cloneFromId: string | null, newName: string) => void;
+ close: () => void;
+}
+
+export interface WorkflowViewProps {
+ workflowId: string;
+ pathname?: string; // From router props
+}
+
+export interface WorkflowHeaderProps {
+ workflows: Workflow[];
+ selectedWorkflowId: string;
+}
+
+export interface WorkflowTabProps {
+ workflowId: string;
+ onDataChange: (
+ payload: { title: string; description: string } | null,
+ ) => void;
+ isDisabled: boolean;
+}
+
+export interface PermissionInfo {
+ perm: string;
+ name: string;
+ description: string;
+}
+
+export interface GroupInfo {
+ id: string;
+ title: string;
+}
+
+export interface ContextData {
+ available_roles: string[];
+ groups: GroupInfo[];
+ managed_permissions: PermissionInfo[];
+}
+
+export interface Workflow {
+ id: string;
+ title: string;
+ description: string;
+ assigned_types: string[];
+ initial_state: string;
+ states: WorkflowState[];
+ transitions: WorkflowTransition[];
+ context_data?: ContextData;
+}
+
+export interface WorkflowState {
+ id: string;
+ title: string;
+ description?: string;
+ isInitial?: boolean;
+ isFinal?: boolean;
+ permissions?: string[];
+ transitions?: string[];
+}
+
+export interface WorkflowTransition {
+ id: string;
+ title: string;
+ new_state: string;
+}
+
+export interface ValidationError {
+ id?: string;
+ title?: string;
+ error: string;
+}
+
+export interface ValidationErrors {
+ state_errors: ValidationError[];
+ transition_errors: ValidationError[];
+ initial_state_error: boolean;
+}
+
+export interface WorkflowReduxState {
+ workflow: {
+ currentWorkflow: Workflow;
+ error: string | null;
+ loaded: boolean;
+ loading: boolean;
+ };
+ workflows: {
+ error: string | null;
+ items: Workflow[];
+ loaded: boolean;
+ loading: boolean;
+ };
+ validation: {
+ error: string | null;
+ errors: ValidationErrors | null;
+ loading: boolean;
+ };
+ operation: {
+ error: string | null;
+ loading: boolean;
+ result: any;
+ };
+ lastCreatedWorkflowId: string | null;
+}
From cfbe266ce41a15990eeccbe832d28c66509532cf Mon Sep 17 00:00:00 2001
From: Manas Kenge <110519001+Manas-Kenge@users.noreply.github.com>
Date: Fri, 1 Aug 2025 17:18:51 +0530
Subject: [PATCH 30/60] add graph types
---
.../src/components/Graph/Edges/CustomEdge.tsx | 18 ++--------
.../src/components/Graph/Nodes/CustomNode.tsx | 15 ++------
.../src/components/Graph/WorkflowGraph.tsx | 25 ++------------
.../src/components/Workflow/WorkflowView.tsx | 2 +-
.../volto-workflow-manager/src/types/graph.ts | 34 +++++++++++++++++++
.../volto-workflow-manager/src/types/index.ts | 6 ----
.../volto-workflow-manager/src/types/state.ts | 1 -
.../src/types/workflow.ts | 9 +++--
8 files changed, 50 insertions(+), 60 deletions(-)
create mode 100644 frontend/packages/volto-workflow-manager/src/types/graph.ts
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/Edges/CustomEdge.tsx b/frontend/packages/volto-workflow-manager/src/components/Graph/Edges/CustomEdge.tsx
index 79a0875..b6a7b36 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/Edges/CustomEdge.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/Edges/CustomEdge.tsx
@@ -1,20 +1,10 @@
import React, { useMemo } from 'react';
import { BaseEdge, getSmoothStepPath, EdgeLabelRenderer } from '@xyflow/react';
import type { EdgeProps } from '@xyflow/react';
-
-interface WorkflowTransitionEdgeData {
- [key: string]: unknown;
- label?: string;
- highlighted?: boolean;
- transitionId?: string;
- description?: string;
- conditions?: string[];
- permissions?: string[];
- automatic?: boolean;
-}
+import type { EdgeData } from '../../../types/graph';
interface CustomEdgeProps extends EdgeProps {
- data?: WorkflowTransitionEdgeData;
+ data?: EdgeData;
pathOptions?: {
borderRadius?: number;
};
@@ -36,7 +26,6 @@ const CustomEdge: React.FC = (props) => {
pathOptions,
} = props;
- // Use getSmoothStepPath to match the playground example
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
@@ -44,7 +33,7 @@ const CustomEdge: React.FC = (props) => {
targetY,
sourcePosition,
targetPosition,
- borderRadius: pathOptions?.borderRadius ?? 10, // Use borderRadius from props
+ borderRadius: pathOptions?.borderRadius ?? 10,
});
const truncatedLabel = useMemo(() => {
@@ -102,4 +91,3 @@ const CustomEdge: React.FC = (props) => {
};
export default CustomEdge;
-export type { WorkflowTransitionEdgeData, CustomEdgeProps };
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/CustomNode.tsx b/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/CustomNode.tsx
index 39f8dd1..3a7a683 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/CustomNode.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/Nodes/CustomNode.tsx
@@ -1,20 +1,10 @@
import React from 'react';
import { Handle, Position, type NodeProps } from '@xyflow/react';
import './index.css';
-
-interface WorkflowStateNodeData {
- [key: string]: unknown;
- label: string;
- highlighted?: boolean;
- stateId?: string;
- description?: string;
- isInitial?: boolean;
- isFinal?: boolean;
- permissions?: string[];
-}
+import type { NodeData } from '../../../types/graph';
interface CustomNodeProps extends NodeProps {
- data: WorkflowStateNodeData;
+ data: NodeData;
}
const CustomNode: React.FC = ({ data, id, selected }) => {
@@ -49,4 +39,3 @@ const CustomNode: React.FC = ({ data, id, selected }) => {
};
export default CustomNode;
-export type { WorkflowStateNodeData, CustomNodeProps };
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
index 6b7ee5e..a6fc2a8 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
@@ -19,7 +19,7 @@ import {
} from '@xyflow/react';
import CustomEdge from './Edges/CustomEdge';
import CustomNode from './Nodes/CustomNode';
-import type { WorkflowTransitionEdgeData } from './Edges/CustomEdge';
+import type { Workflow, EdgeData } from '../../types/graph';
import '@xyflow/react/dist/style.css';
const fitViewOptions: FitViewOptions = { padding: 0.2 };
@@ -30,25 +30,6 @@ const defaultEdgeOptions: DefaultEdgeOptions = {
const edgeTypes = { custom: CustomEdge };
const nodeTypes = { custom: CustomNode };
-interface WorkflowState {
- id: string;
- title: string;
- transitions: string[];
-}
-interface WorkflowTransition {
- id: string;
- title: string;
- new_state_id: string;
-}
-interface Workflow {
- id: string;
- title: string;
- description: string;
- initial_state: string;
- states: WorkflowState[];
- transitions: WorkflowTransition[];
-}
-
interface WorkflowGraphProps {
workflow: Workflow;
}
@@ -79,10 +60,10 @@ const WorkflowGraphInner: React.FC = ({ workflow }) => {
});
}, [workflow]);
- const initialEdges = useMemo[]>(() => {
+ const initialEdges = useMemo[]>(() => {
if (!workflow?.states || !workflow?.transitions) return [];
- const edges: Edge[] = [];
+ const edges: Edge[] = [];
const transitionsMap = new Map(workflow.transitions.map((t) => [t.id, t]));
workflow.states.forEach((state) => {
diff --git a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
index 35297f4..d5121e6 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowView.tsx
@@ -89,7 +89,7 @@ const WorkflowView: React.FC = ({
{isClient &&
createPortal(
diff --git a/frontend/packages/volto-workflow-manager/src/types/graph.ts b/frontend/packages/volto-workflow-manager/src/types/graph.ts
new file mode 100644
index 0000000..d3d8864
--- /dev/null
+++ b/frontend/packages/volto-workflow-manager/src/types/graph.ts
@@ -0,0 +1,34 @@
+export interface WorkflowState {
+ id: string;
+ title: string;
+ transitions: string[];
+}
+
+export interface WorkflowTransition {
+ id: string;
+ title: string;
+ new_state_id: string;
+}
+
+export interface Workflow {
+ id: string;
+ title: string;
+ description: string;
+ initial_state: string;
+ states: WorkflowState[];
+ transitions: WorkflowTransition[];
+}
+
+export interface NodeData {
+ [key: string]: unknown;
+ label: string;
+ description?: string;
+ isInitial?: boolean;
+ isFinal?: boolean;
+}
+
+export interface EdgeData {
+ [key: string]: unknown;
+ label?: string;
+ description?: string;
+}
diff --git a/frontend/packages/volto-workflow-manager/src/types/index.ts b/frontend/packages/volto-workflow-manager/src/types/index.ts
index 36d63f3..3639d51 100644
--- a/frontend/packages/volto-workflow-manager/src/types/index.ts
+++ b/frontend/packages/volto-workflow-manager/src/types/index.ts
@@ -4,15 +4,9 @@ import {
useSelector,
} from 'react-redux';
import type { WorkflowReduxState } from './workflow';
-import type { WorkflowState } from './workflow';
import type { StateReduxState } from './state';
import type { TransitionReduxState } from './transition';
-export type UpdateStatePayload = Partial> & {
- is_initial_state?: boolean;
- states_with_this_transition?: string[];
-};
-
export interface GlobalRootState {
workflow: WorkflowReduxState;
state: StateReduxState;
diff --git a/frontend/packages/volto-workflow-manager/src/types/state.ts b/frontend/packages/volto-workflow-manager/src/types/state.ts
index c4e6bc5..0420617 100644
--- a/frontend/packages/volto-workflow-manager/src/types/state.ts
+++ b/frontend/packages/volto-workflow-manager/src/types/state.ts
@@ -111,7 +111,6 @@ export interface DeleteStateResponse {
message: string;
}
-// Define the shape of the "state" slice in the Redux store
export interface StateReduxState {
get: {
loading: boolean;
diff --git a/frontend/packages/volto-workflow-manager/src/types/workflow.ts b/frontend/packages/volto-workflow-manager/src/types/workflow.ts
index 172b07a..c97d4cc 100644
--- a/frontend/packages/volto-workflow-manager/src/types/workflow.ts
+++ b/frontend/packages/volto-workflow-manager/src/types/workflow.ts
@@ -6,7 +6,7 @@ export interface CreateWorkflowProps {
export interface WorkflowViewProps {
workflowId: string;
- pathname?: string; // From router props
+ pathname?: string;
}
export interface WorkflowHeaderProps {
@@ -39,6 +39,11 @@ export interface ContextData {
managed_permissions: PermissionInfo[];
}
+export type UpdateStatePayload = Partial> & {
+ is_initial_state?: boolean;
+ states_with_this_transition?: string[];
+};
+
export interface Workflow {
id: string;
title: string;
@@ -63,7 +68,7 @@ export interface WorkflowState {
export interface WorkflowTransition {
id: string;
title: string;
- new_state: string;
+ new_state_id: string; // Fixed: was "new_state", now matches usage in WorkflowGraph
}
export interface ValidationError {
From ca607deff121509cceb7a03c2d43489bf2074900 Mon Sep 17 00:00:00 2001
From: Manas Kenge <110519001+Manas-Kenge@users.noreply.github.com>
Date: Sat, 2 Aug 2025 13:23:10 +0530
Subject: [PATCH 31/60] add create transition dialog
---
.../src/components/States/CreateState.tsx | 106 ++++---
.../src/components/States/State.tsx | 6 +-
.../Transitions/CreateTransition.tsx | 294 ++++++++++++++++++
.../src/components/Transitions/Transition.tsx | 6 +-
.../components/Workflow/ActionsToolbar.tsx | 12 +-
5 files changed, 371 insertions(+), 53 deletions(-)
create mode 100644 frontend/packages/volto-workflow-manager/src/components/Transitions/CreateTransition.tsx
diff --git a/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx b/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
index 7b8214a..6fcf9b7 100644
--- a/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/States/CreateState.tsx
@@ -13,8 +13,10 @@ import {
TextField,
View,
ProgressCircle,
- StatusLight,
} from '@adobe/react-spectrum';
+import { toast } from 'react-toastify';
+import Toast from '@plone/volto/components/manage/Toast/Toast';
+import { useIntl, defineMessages } from 'react-intl';
import { addState, getWorkflows } from '../../actions';
import { useAppDispatch, useAppSelector } from '../../types';
import type { RootState } from '../../types';
@@ -25,48 +27,70 @@ interface CreateStateProps {
onClose: () => void;
}
+const messages = defineMessages({
+ creationSuccessTitle: {
+ id: 'State Created',
+ defaultMessage: 'State Created',
+ },
+ creationSuccessContent: {
+ id: 'The new state has been added successfully.',
+ defaultMessage: 'The new state has been added successfully.',
+ },
+ creationErrorTitle: {
+ id: 'State Creation Failed',
+ defaultMessage: 'State Creation Failed',
+ },
+});
+
const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
const dispatch = useAppDispatch();
+ const intl = useIntl();
const [cloneFromStateId, setCloneFromStateId] = useState(null);
const [newStateTitle, setNewStateTitle] = useState('');
const [newStateDescription, setNewStateDescription] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
- const [submitError, setSubmitError] = useState(null);
- const [submitSuccess, setSubmitSuccess] = useState(false);
const currentWorkflow = useAppSelector((state: RootState) =>
state.workflow.workflows.items.find((wf) => wf.id === workflowId),
);
- // Get add state status from Redux store
const addStateStatus = useAppSelector((state: RootState) => state.state?.add);
- // Reset form when dialog opens/closes
useEffect(() => {
if (!isOpen) {
setCloneFromStateId(null);
setNewStateTitle('');
setNewStateDescription('');
setIsSubmitting(false);
- setSubmitError(null);
- setSubmitSuccess(false);
}
}, [isOpen]);
- // Handle add state response
useEffect(() => {
if (addStateStatus?.loaded && isSubmitting) {
setIsSubmitting(false);
- setSubmitSuccess(true);
- // Auto-close after success (optional)
+ setNewStateTitle('');
+ toast.success(
+ ,
+ );
setTimeout(() => {
onClose();
}, 1500);
} else if (addStateStatus?.error && isSubmitting) {
setIsSubmitting(false);
- setSubmitError(addStateStatus.error);
+ const errorMessage = addStateStatus.error;
+ toast.error(
+ ,
+ );
}
- }, [addStateStatus, isSubmitting, onClose]);
+ }, [addStateStatus, isSubmitting, onClose, intl]);
const validateStateTitle = (title: string): string | null => {
if (!title.trim()) {
@@ -75,11 +99,7 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
if (title.length < 2) {
return 'State title must be at least 2 characters';
}
- if (title.length > 50) {
- return 'State title must be less than 50 characters';
- }
- // Check if state with similar ID might already exist
const stateId = title.trim().replace(/\s+/g, '_').toLowerCase();
const existingState = currentWorkflow?.states?.find(
(state) =>
@@ -93,18 +113,22 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
return null;
};
- const handleCreate = async (closeDialog: () => void) => {
+ const handleCreate = async () => {
if (!newStateTitle || !currentWorkflow) return;
const titleError = validateStateTitle(newStateTitle);
if (titleError) {
- setSubmitError(titleError);
+ toast.error(
+ ,
+ );
return;
}
setIsSubmitting(true);
- setSubmitError(null);
- setSubmitSuccess(false);
try {
const stateData: {
@@ -126,16 +150,21 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
const result = await dispatch(addState(currentWorkflow.id, stateData));
if (result && !result.error) {
- // Refresh workflows to get updated state list
await dispatch(getWorkflows());
- // Don't close immediately - let the useEffect handle it after success state
} else {
setIsSubmitting(false);
- setSubmitError(result?.error || 'Failed to create state');
+ // The useEffect hook will handle the toast for the API error
}
} catch (error: any) {
setIsSubmitting(false);
- setSubmitError(error?.message || 'An unexpected error occurred');
+ const errorMessage = error?.message || 'An unexpected error occurred';
+ toast.error(
+ ,
+ );
}
};
@@ -168,23 +197,6 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
UNSAFE_style={{ height: '1px', backgroundColor: '#e1e1e1' }}
/>
- {/* Success Message */}
- {submitSuccess && (
-
-
- State created successfully! Closing dialog...
-
-
- )}
-
- {/* Error Message */}
- {submitError && (
-
- {submitError}
-
- )}
-
- {/* Clone From Section */}
Clone from existing state (optional)
@@ -207,14 +219,11 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
- {/* State Name Section */}
State Name *
{
validationState={titleError ? 'invalid' : 'valid'}
errorMessage={titleError}
isDisabled={isSubmitting}
- placeholder="Enter state name..."
+ description="Enter state name..."
/>
- {/* State Description Section */}
Description (optional)
@@ -252,7 +258,7 @@ const CreateState = ({ workflowId, isOpen, onClose }: CreateStateProps) => {
-
+ setCreateTransitionOpen(true)}
+ >
Add Transition
@@ -198,6 +203,11 @@ const ActionsToolbar = ({ workflowId }: { workflowId: string }) => {
isOpen={isCreateStateOpen}
onClose={() => setCreateStateOpen(false)}
/>
+ setCreateTransitionOpen(false)}
+ />
>
);
};
From e00319033f12fd6fa99e5499e9e115ed72e63530 Mon Sep 17 00:00:00 2001
From: Manas Kenge <110519001+Manas-Kenge@users.noreply.github.com>
Date: Wed, 6 Aug 2025 12:10:26 +0530
Subject: [PATCH 32/60] fix validation/sanity-check
---
.../api/services/workflow/configure.zcml | 2 +-
.../api/services/workflow/validators.py | 43 -------
.../manager/api/services/workflow/workflow.py | 33 ++---
.../src/actions/workflow.ts | 9 +-
.../components/Workflow/ActionsToolbar.tsx | 95 +++------------
.../Workflow/WorkflowValidation.tsx | 113 ++++++++++++++++++
.../src/constants/index.ts | 2 +-
.../src/reducers/workflow.ts | 9 +-
8 files changed, 164 insertions(+), 142 deletions(-)
delete mode 100644 backend/src/workflow/manager/api/services/workflow/validators.py
create mode 100644 frontend/packages/volto-workflow-manager/src/components/Workflow/WorkflowValidation.tsx
diff --git a/backend/src/workflow/manager/api/services/workflow/configure.zcml b/backend/src/workflow/manager/api/services/workflow/configure.zcml
index a92e3fb..7e9913f 100644
--- a/backend/src/workflow/manager/api/services/workflow/configure.zcml
+++ b/backend/src/workflow/manager/api/services/workflow/configure.zcml
@@ -58,7 +58,7 @@
permission="cmf.ManagePortal"
/>
-
+
{
const validation = useAppSelector((state) => state.workflow.validation);
const wasLoading = usePrevious(validation.loading);
+ useEffect(() => {
+ dispatch(clearValidation());
+ }, [dispatch, workflowId]);
+
useEffect(() => {
if (wasLoading && !validation.loading) {
const errors = validation.errors;
@@ -88,12 +86,6 @@ const ActionsToolbar = ({ workflowId }: { workflowId: string }) => {
}
}, [validation, wasLoading, intl]);
- const hasErrors =
- validation.errors &&
- (validation.errors.state_errors.length > 0 ||
- validation.errors.transition_errors.length > 0 ||
- validation.errors.initial_state_error);
-
const handleSanityCheck = () => {
dispatch(validateWorkflow(workflowId));
};
@@ -119,7 +111,11 @@ const ActionsToolbar = ({ workflowId }: { workflowId: string }) => {
isDisabled={validation.loading}
>
{validation.loading ? (
-
+
) : (
)}
@@ -131,71 +127,10 @@ const ActionsToolbar = ({ workflowId }: { workflowId: string }) => {
- {validation.errors && (
-
-
- {hasErrors ? 'View Validation Errors' : 'View Validation Results'}
-
- {(close) => (
-
- {hasErrors ? (
-
- {validation.errors.initial_state_error && (
-
- Initial State Error: The workflow must have a
- valid initial state.
-
-
-
- )}
- {validation.errors.state_errors.length > 0 && (
- <>
- State Errors
- {validation.errors.state_errors.map((err, index) => (
-
- {err.title || err.id}: {err.error}
-
-
- ))}
-
- >
- )}
- {validation.errors.transition_errors.length > 0 && (
- <>
- Transition Errors
- {validation.errors.transition_errors.map(
- (err, index) => (
-
- {err.title || err.id}: {err.error}
-
-
- ),
- )}
- >
- )}
-
- ) : (
- No validation errors found.
- )}
-
- )}
-
- )}
+
= ({
+ validationErrors,
+ workflowId,
+}) => {
+ if (!validationErrors) {
+ return null;
+ }
+
+ const hasErrors =
+ validationErrors.state_errors.length > 0 ||
+ validationErrors.transition_errors.length > 0 ||
+ validationErrors.initial_state_error;
+
+ return (
+
+
+ {hasErrors ? 'View Validation Errors' : 'View Validation Results'}
+
+ {(close) => (
+
+ {hasErrors ? (
+
+ {validationErrors.initial_state_error && (
+
+ Initial State Error: The workflow must have a valid
+ initial state.
+
+
+
+ )}
+ {validationErrors.state_errors.length > 0 && (
+ <>
+ State Errors
+ {validationErrors.state_errors.map((err, index) => (
+
+
+
+ {err.title || err.id}
+
+
+ : {err.error}
+
+
+ ))}
+
+ >
+ )}
+ {validationErrors.transition_errors.length > 0 && (
+ <>
+ Transition Errors
+ {validationErrors.transition_errors.map((err, index) => (
+
+
+
+ {err.title || err.id}
+
+
+ : {err.error}
+
+
+ ))}
+ >
+ )}
+
+ ) : (
+ No validation errors found.
+ )}
+
+ )}
+
+ );
+};
+
+export default WorkflowValidation;
diff --git a/frontend/packages/volto-workflow-manager/src/constants/index.ts b/frontend/packages/volto-workflow-manager/src/constants/index.ts
index 1d3158f..1312074 100644
--- a/frontend/packages/volto-workflow-manager/src/constants/index.ts
+++ b/frontend/packages/volto-workflow-manager/src/constants/index.ts
@@ -4,9 +4,9 @@ export const ADD_WORKFLOW = 'ADD_WORKFLOW' as const;
export const DELETE_WORKFLOW = 'DELETE_WORKFLOW' as const;
export const UPDATE_WORKFLOW = 'UPDATE_WORKFLOW' as const;
export const UPDATE_WORKFLOW_SECURITY = 'UPDATE_WORKFLOW_SECURITY' as const;
-export const UPDATE_WORKFLOW_STATE = 'UPDATE_WORKFLOW_STATE';
export const ASSIGN_WORKFLOW = 'ASSIGN_WORKFLOW' as const;
export const VALIDATE_WORKFLOW = 'VALIDATE_WORKFLOW' as const;
+export const CLEAR_VALIDATION = 'CLEAR_VALIDATION' as const;
export const CLEAR_LAST_CREATED_WORKFLOW = 'CLEAR_LAST_CREATED_WORKFLOW';
export const LIST_STATES = 'LIST_STATES';
diff --git a/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts b/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
index 8f7ce87..828d1fa 100644
--- a/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
+++ b/frontend/packages/volto-workflow-manager/src/reducers/workflow.ts
@@ -8,6 +8,7 @@ import {
UPDATE_WORKFLOW_SECURITY,
ASSIGN_WORKFLOW,
VALIDATE_WORKFLOW,
+ CLEAR_VALIDATION,
CLEAR_LAST_CREATED_WORKFLOW,
} from '../constants';
import type { WorkflowReduxState } from '../types/workflow';
@@ -337,7 +338,13 @@ export default function workflow(
error: action.error || 'Failed to validate workflow',
},
};
-
+ case CLEAR_VALIDATION:
+ return {
+ ...state,
+ validation: {
+ ...initialState.validation, // Resets errors, error, and loading
+ },
+ };
// Clear last created workflow ID
case CLEAR_LAST_CREATED_WORKFLOW:
return {
From 8a099d8dce3d0b05173e71e2cae1870812c23f22 Mon Sep 17 00:00:00 2001
From: Manas Kenge <110519001+Manas-Kenge@users.noreply.github.com>
Date: Mon, 11 Aug 2025 12:21:51 +0530
Subject: [PATCH 33/60] add transition dialog on connecting the nodes
---
.../src/actions/transition.ts | 54 +++++++
.../src/components/Graph/WorkflowGraph.tsx | 118 ++++++++------
.../Transitions/CreateTransition.tsx | 152 +++++++++---------
3 files changed, 195 insertions(+), 129 deletions(-)
diff --git a/frontend/packages/volto-workflow-manager/src/actions/transition.ts b/frontend/packages/volto-workflow-manager/src/actions/transition.ts
index ed9c565..bf296fc 100644
--- a/frontend/packages/volto-workflow-manager/src/actions/transition.ts
+++ b/frontend/packages/volto-workflow-manager/src/actions/transition.ts
@@ -5,6 +5,8 @@ import {
UPDATE_TRANSITION,
DELETE_TRANSITION,
} from '../constants';
+import { updateState } from './state';
+import { getWorkflows } from './workflow';
export interface AddTransitionPayload {
title: string;
@@ -132,3 +134,55 @@ export function deleteTransition(workflowId: string, transitionId: string) {
},
};
}
+
+export const createAndLinkTransition = (
+ workflowId: string,
+ transitionId: string,
+ payload: any,
+ sourceStateId: string | null,
+) => {
+ return async (dispatch, getState) => {
+ try {
+ const result = await dispatch(
+ addTransition(workflowId, transitionId, payload),
+ );
+ if (result && result.error) {
+ return result;
+ }
+
+ if (sourceStateId) {
+ const { workflow } = getState();
+ const currentWorkflow = workflow.workflows.items.find(
+ (w) => w.id === workflowId,
+ );
+ const sourceState = currentWorkflow?.states.find(
+ (s) => s.id === sourceStateId,
+ );
+
+ if (sourceState) {
+ const newTransitionsForState = [
+ ...(sourceState.transitions || []),
+ transitionId,
+ ];
+ const updatePayload = {
+ id: sourceStateId,
+ transitions: newTransitionsForState,
+ };
+ const updateResult = await dispatch(
+ updateState(workflowId, sourceStateId, updatePayload),
+ );
+
+ if (updateResult && updateResult.error) {
+ return updateResult;
+ }
+ }
+ }
+
+ await dispatch(getWorkflows());
+
+ return result;
+ } catch (error) {
+ return { error: { message: 'An unexpected error occurred.' } };
+ }
+ };
+};
diff --git a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
index a6fc2a8..6b8ecc9 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Graph/WorkflowGraph.tsx
@@ -1,18 +1,15 @@
-import { useMemo, useCallback } from 'react';
+import { useCallback, useState, useEffect } from 'react';
import {
ReactFlow,
ReactFlowProvider,
Controls,
Background,
MiniMap,
- addEdge,
reconnectEdge,
useEdgesState,
useNodesState,
- type Node,
type Edge,
type FitViewOptions,
- type OnConnect,
type OnReconnect,
type DefaultEdgeOptions,
type Connection,
@@ -21,6 +18,7 @@ import CustomEdge from './Edges/CustomEdge';
import CustomNode from './Nodes/CustomNode';
import type { Workflow, EdgeData } from '../../types/graph';
import '@xyflow/react/dist/style.css';
+import CreateTransition from '../Transitions/CreateTransition';
const fitViewOptions: FitViewOptions = { padding: 0.2 };
const defaultEdgeOptions: DefaultEdgeOptions = {
@@ -35,13 +33,20 @@ interface WorkflowGraphProps {
}
const WorkflowGraphInner: React.FC = ({ workflow }) => {
- const initialNodes = useMemo(() => {
- if (!workflow?.states) return [];
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
+ const [isCreateTransitionOpen, setCreateTransitionOpen] = useState(false);
+ const [newTransitionInfo, setNewTransitionInfo] = useState<{
+ source: string;
+ target: string;
+ } | null>(null);
+
+ useEffect(() => {
+ if (!workflow) return;
const nodeCount = workflow.states.length;
const radius = Math.max(200, nodeCount * 40);
-
- return workflow.states.map((state, idx) => {
+ const newNodes = workflow.states.map((state, idx) => {
const angle = (idx * 2 * Math.PI) / nodeCount;
return {
id: state.id,
@@ -58,26 +63,20 @@ const WorkflowGraphInner: React.FC = ({ workflow }) => {
},
};
});
- }, [workflow]);
-
- const initialEdges = useMemo[]>(() => {
- if (!workflow?.states || !workflow?.transitions) return [];
-
- const edges: Edge[] = [];
- const transitionsMap = new Map(workflow.transitions.map((t) => [t.id, t]));
+ const newEdges: Edge[] = [];
+ const transitionsMap = new Map(
+ (workflow.transitions || []).map((t) => [t.id, t]),
+ );
workflow.states.forEach((state) => {
- state.transitions.forEach((transitionId) => {
+ (state.transitions || []).forEach((transitionId) => {
const transition = transitionsMap.get(transitionId);
if (!transition) return;
- const sourceId = state.id;
- const targetId = transition.new_state_id;
-
- edges.push({
- id: `${sourceId}-${transitionId}-${targetId}`,
- source: sourceId,
- target: targetId,
+ newEdges.push({
+ id: `${state.id}-${transitionId}-${transition.new_state_id}`,
+ source: state.id,
+ target: transition.new_state_id,
type: 'custom',
data: {
label: transition.title,
@@ -91,18 +90,22 @@ const WorkflowGraphInner: React.FC = ({ workflow }) => {
});
});
- return edges;
- }, [workflow]);
+ setNodes(newNodes);
+ setEdges(newEdges);
+ }, [workflow, setNodes, setEdges]);
- const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
- const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+ const onConnect = useCallback((connection: Connection) => {
+ setNewTransitionInfo({
+ source: connection.source,
+ target: connection.target,
+ });
+ setCreateTransitionOpen(true);
+ }, []);
- const onConnect: OnConnect = useCallback(
- (connection: Connection) => {
- setEdges((eds) => addEdge({ ...connection, type: 'custom' }, eds));
- },
- [setEdges],
- );
+ const handleCloseCreateDialog = () => {
+ setCreateTransitionOpen(false);
+ setNewTransitionInfo(null);
+ };
const onReconnect: OnReconnect = useCallback(
(oldEdge, newConnection) => {
@@ -112,26 +115,35 @@ const WorkflowGraphInner: React.FC = ({ workflow }) => {
);
return (
-
-
-
-
- (node.selected ? '#0078d4' : '#ddd')} />
-
-
+ <>
+
+
+
+
+ (node.selected ? '#0078d4' : '#ddd')} />
+
+
+
+ >
);
};
diff --git a/frontend/packages/volto-workflow-manager/src/components/Transitions/CreateTransition.tsx b/frontend/packages/volto-workflow-manager/src/components/Transitions/CreateTransition.tsx
index a29c203..3bb7d25 100644
--- a/frontend/packages/volto-workflow-manager/src/components/Transitions/CreateTransition.tsx
+++ b/frontend/packages/volto-workflow-manager/src/components/Transitions/CreateTransition.tsx
@@ -17,14 +17,19 @@ import {
import { toast } from 'react-toastify';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import { useIntl, defineMessages } from 'react-intl';
-import { addTransition, getWorkflows } from '../../actions';
-import { useAppDispatch, useAppSelector } from '../../types';
-import type { RootState } from '../../types';
+import { createAndLinkTransition } from '../../actions/transition';
+import {
+ type GlobalRootState,
+ useAppDispatch,
+ useAppSelector,
+} from '../../types';
export interface CreateTransitionProps {
workflowId: string;
isOpen: boolean;
onClose: () => void;
+ initialSourceStateId?: string | null;
+ initialDestinationStateId?: string | null;
}
const messages = defineMessages({
@@ -46,6 +51,8 @@ const CreateTransition = ({
workflowId,
isOpen,
onClose,
+ initialSourceStateId,
+ initialDestinationStateId,
}: CreateTransitionProps) => {
const dispatch = useAppDispatch();
const intl = useIntl();
@@ -55,50 +62,25 @@ const CreateTransition = ({
const [newTransitionTitle, setNewTransitionTitle] = useState('');
const [newTransitionDescription, setNewTransitionDescription] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
-
- const currentWorkflow = useAppSelector((state: RootState) =>
- state.workflow.workflows.items.find((wf) => wf.id === workflowId),
+ const [destinationStateId, setDestinationStateId] = useState(
+ null,
);
- const addTransitionStatus = useAppSelector(
- (state: RootState) => state.transition?.add,
+ const currentWorkflow = useAppSelector((state: GlobalRootState) =>
+ state.workflow.workflows.items.find((wf) => wf.id === workflowId),
);
useEffect(() => {
- if (!isOpen) {
+ if (isOpen) {
+ setDestinationStateId(initialDestinationStateId || null);
+ } else {
setCloneFromTransitionId(null);
setNewTransitionTitle('');
setNewTransitionDescription('');
setIsSubmitting(false);
+ setDestinationStateId(null);
}
- }, [isOpen]);
-
- useEffect(() => {
- if (addTransitionStatus?.loaded && isSubmitting) {
- setIsSubmitting(false);
- setNewTransitionTitle('');
- toast.success(
- ,
- );
- setTimeout(() => {
- onClose();
- }, 1500);
- } else if (addTransitionStatus?.error && isSubmitting) {
- setIsSubmitting(false);
- const errorMessage = addTransitionStatus.error;
- toast.error(
- ,
- );
- }
- }, [addTransitionStatus, isSubmitting, onClose, intl]);
+ }, [isOpen, initialDestinationStateId]);
const validateTransitionTitle = (title: string): string | null => {
if (!title.trim()) {
@@ -122,8 +104,8 @@ const CreateTransition = ({
};
const handleCreate = async () => {
- if (!newTransitionTitle || !currentWorkflow) return;
-
+ if (!newTransitionTitle || !destinationStateId || !currentWorkflow) return;
+ const sourceStateId = initialSourceStateId;
const titleError = validateTransitionTitle(newTransitionTitle);
if (titleError) {
toast.error(
@@ -138,48 +120,33 @@ const CreateTransition = ({
setIsSubmitting(true);
- try {
- const transitionId = newTransitionTitle
- .trim()
- .replace(/\s+/g, '_')
- .toLowerCase();
+ const transitionId = newTransitionTitle
+ .trim()
+ .replace(/\s+/g, '_')
+ .toLowerCase();
- const payload: {
- title: string;
- description?: string;
- clone_from_id?: string;
- } = {
- title: newTransitionTitle.trim(),
- };
+ const payload = {
+ title: newTransitionTitle.trim(),
+ new_state_id: destinationStateId,
+ description: newTransitionDescription.trim() || undefined,
+ clone_from_id: cloneFromTransitionId || undefined,
+ };
- if (newTransitionDescription.trim()) {
- payload.description = newTransitionDescription.trim();
- }
-
- if (cloneFromTransitionId) {
- payload.clone_from_id = cloneFromTransitionId;
- }
+ const result = await dispatch(
+ createAndLinkTransition(workflowId, transitionId, payload, sourceStateId),
+ );
- const result = await dispatch(
- addTransition(currentWorkflow.id, transitionId, payload),
- );
+ setIsSubmitting(false);
- if (result && !result.error) {
- await dispatch(getWorkflows());
- } else {
- setIsSubmitting(false);
- // The useEffect hook will handle the toast for the API error
- }
- } catch (error: any) {
- setIsSubmitting(false);
- const errorMessage = error?.message || 'An unexpected error occurred';
- toast.error(
+ if (result && !result.error) {
+ toast.success(
,
);
+ onClose();
}
};
@@ -190,7 +157,11 @@ const CreateTransition = ({
const titleError = newTransitionTitle
? validateTransitionTitle(newTransitionTitle)
: null;
- const isFormValid = newTransitionTitle.trim() && !titleError && !isSubmitting;
+ const isFormValid =
+ newTransitionTitle.trim() &&
+ destinationStateId &&
+ !titleError &&
+ !isSubmitting;
return (
setCloneFromTransitionId(selected as string)
@@ -238,11 +210,32 @@ const CreateTransition = ({