- Introduced a new page for executing business process workflows, allowing users to run and interact with processes directly. - Added functionality to navigate to the workflow execution page from the process management interface. - Enhanced the user experience with loading states and dynamic step execution feedback. - Updated the process management page to include a button for running workflows, improving accessibility to process execution features. - Ensured integration with existing form and conditional logic components for seamless workflow execution.
614 lines
23 KiB
Vue
614 lines
23 KiB
Vue
<script setup>
|
|
import { ref, computed, onMounted, watch } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import FormScriptEngine from '~/components/FormScriptEngine.vue';
|
|
import ConditionalLogicEngine from '~/components/ConditionalLogicEngine.vue';
|
|
import ComponentPreview from '~/components/ComponentPreview.vue';
|
|
import { useFormBuilderStore } from '~/stores/formBuilder';
|
|
import { FormKit } from '@formkit/vue';
|
|
|
|
// Define page meta
|
|
definePageMeta({
|
|
title: "Process Execution",
|
|
description: "Execute and run through a business process workflow",
|
|
layout: "empty",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
});
|
|
|
|
// Get route and router
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
// Get form builder store for ComponentPreview data sharing
|
|
const formStore = useFormBuilderStore();
|
|
|
|
// State
|
|
const loading = ref(true);
|
|
const process = ref(null);
|
|
const currentStep = ref(0);
|
|
const caseInstance = ref(null);
|
|
const formData = ref({});
|
|
const processVariables = ref({});
|
|
const error = ref(null);
|
|
const stepLoading = ref(false);
|
|
const currentForm = ref(null);
|
|
const tasks = ref([]);
|
|
const conditionalLogicScript = ref('');
|
|
const combinedScript = ref('');
|
|
|
|
// Get process ID from route
|
|
const processId = computed(() => route.params.id);
|
|
|
|
// Get current workflow definition
|
|
const workflowData = computed(() => {
|
|
if (!process.value?.processDefinition) return null;
|
|
return process.value.processDefinition;
|
|
});
|
|
|
|
// Get current step node
|
|
const currentNode = computed(() => {
|
|
if (!workflowData.value?.nodes || currentStep.value >= workflowData.value.nodes.length) {
|
|
return null;
|
|
}
|
|
return workflowData.value.nodes[currentStep.value];
|
|
});
|
|
|
|
// Get next step
|
|
const nextNode = computed(() => {
|
|
if (!workflowData.value?.nodes || currentStep.value + 1 >= workflowData.value.nodes.length) {
|
|
return null;
|
|
}
|
|
return workflowData.value.nodes[currentStep.value + 1];
|
|
});
|
|
|
|
// Check if process is complete
|
|
const isProcessComplete = computed(() => {
|
|
return currentNode.value?.type === 'end' || currentStep.value >= (workflowData.value?.nodes?.length || 0);
|
|
});
|
|
|
|
// Load process data
|
|
const loadProcess = async () => {
|
|
try {
|
|
loading.value = true;
|
|
error.value = null;
|
|
|
|
console.log('[Workflow] Loading process definition...');
|
|
const response = await $fetch(`/api/process/${processId.value}`);
|
|
|
|
if (response.success) {
|
|
process.value = response.process; // includes processDefinition
|
|
console.log('[Workflow] Process loaded:', process.value.processName, process.value.processDefinition);
|
|
|
|
// Check if process is published
|
|
const status = process.value.processStatus || process.value.status || 'draft';
|
|
if (status !== 'published') {
|
|
error.value = 'Process must be published before execution';
|
|
return;
|
|
}
|
|
|
|
// Initialize process variables from DB (process.processVariables)
|
|
processVariables.value = process.value.processVariables ? { ...process.value.processVariables } : {};
|
|
|
|
// Start the process execution (case instance)
|
|
await startProcessExecution();
|
|
|
|
} else {
|
|
error.value = response.message || 'Failed to load process';
|
|
}
|
|
} catch (err) {
|
|
console.error('[Workflow] Error loading process:', err);
|
|
error.value = 'Failed to load process data';
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Start process execution (create case instance)
|
|
const startProcessExecution = async () => {
|
|
try {
|
|
console.log('[Workflow] Starting process execution (creating case instance)...');
|
|
const response = await $fetch(`/api/process/${processId.value}/start`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (response.success) {
|
|
caseInstance.value = response.data.case;
|
|
tasks.value = response.data.tasks;
|
|
console.log('[Workflow] Case instance created:', caseInstance.value);
|
|
// Find the start node
|
|
const startNodeIndex = workflowData.value.nodes.findIndex(node => node.type === 'start');
|
|
currentStep.value = startNodeIndex >= 0 ? startNodeIndex : 0;
|
|
console.log('[Workflow] Starting at node index:', currentStep.value, workflowData.value.nodes[currentStep.value]);
|
|
moveToNextStep();
|
|
} else {
|
|
throw new Error(response.error || 'Failed to start process');
|
|
}
|
|
} catch (err) {
|
|
console.error('[Workflow] Error starting process execution:', err);
|
|
error.value = 'Failed to start process execution';
|
|
}
|
|
};
|
|
|
|
// Helper: Get next node ID by following edges
|
|
function getNextNodeId(currentNodeId) {
|
|
const edge = workflowData.value.edges.find(e => e.source === currentNodeId);
|
|
return edge ? edge.target : null;
|
|
}
|
|
|
|
// Move to next step in workflow (edge-based)
|
|
const moveToNextStep = () => {
|
|
const currentNode = workflowData.value.nodes[currentStep.value];
|
|
if (!currentNode) return;
|
|
const nextNodeId = getNextNodeId(currentNode.id);
|
|
if (nextNodeId) {
|
|
const nextIndex = workflowData.value.nodes.findIndex(n => n.id === nextNodeId);
|
|
if (nextIndex !== -1) {
|
|
currentStep.value = nextIndex;
|
|
const node = workflowData.value.nodes[currentStep.value];
|
|
console.log(`[Workflow] Entered node: ${node.type} - ${node.data?.label || node.label}`);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle form submission
|
|
const handleFormSubmit = async () => {
|
|
try {
|
|
stepLoading.value = true;
|
|
|
|
console.log('[Workflow] Form submitted. Data:', formData.value);
|
|
// Save form data to process variables
|
|
Object.assign(processVariables.value, formData.value);
|
|
|
|
// Move to next step
|
|
moveToNextStep();
|
|
|
|
console.log('[Workflow] After form submit, current node:', currentNode.value);
|
|
|
|
// If next step is API or script, execute it automatically
|
|
if (currentNode.value && ['api', 'script'].includes(currentNode.value.type)) {
|
|
await executeCurrentStep();
|
|
}
|
|
} catch (err) {
|
|
console.error('[Workflow] Error submitting form:', err);
|
|
error.value = 'Failed to submit form';
|
|
} finally {
|
|
stepLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// Execute current step (for API/script nodes)
|
|
const executeCurrentStep = async () => {
|
|
try {
|
|
stepLoading.value = true;
|
|
|
|
console.log('[Workflow] Executing current step:', currentNode.value);
|
|
|
|
if (currentNode.value?.type === 'api') {
|
|
console.log(`[Workflow] Executing API node: ${currentNode.value.data?.label || currentNode.value.label}`);
|
|
// Enhanced API node execution
|
|
const {
|
|
apiUrl,
|
|
apiMethod = 'GET',
|
|
headers = '{}',
|
|
requestBody = '',
|
|
outputVariable = 'apiResponse',
|
|
errorVariable = 'apiError',
|
|
continueOnError = false
|
|
} = currentNode.value.data || {};
|
|
try {
|
|
const response = await $fetch(apiUrl, {
|
|
method: apiMethod,
|
|
headers: headers ? JSON.parse(headers) : {},
|
|
body: requestBody ? JSON.parse(requestBody) : undefined,
|
|
});
|
|
processVariables.value[outputVariable] = response;
|
|
processVariables.value[errorVariable] = null;
|
|
console.log('[Workflow] API call success. Output variable set:', outputVariable, response);
|
|
moveToNextStep();
|
|
} catch (err) {
|
|
processVariables.value[errorVariable] = err;
|
|
console.error('[Workflow] API call failed:', err);
|
|
if (continueOnError) {
|
|
moveToNextStep();
|
|
} else {
|
|
error.value = 'API call failed: ' + (err.message || err);
|
|
}
|
|
}
|
|
} else if (currentNode.value?.type === 'script') {
|
|
console.log(`[Workflow] Executing script node: ${currentNode.value.data?.label || currentNode.value.label}`);
|
|
// Simulate script execution
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
moveToNextStep();
|
|
}
|
|
// Add more node types as needed
|
|
} catch (err) {
|
|
console.error('[Workflow] Error executing step:', err);
|
|
error.value = 'Failed to execute step';
|
|
} finally {
|
|
stepLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// Get step type display info
|
|
const getStepInfo = (node) => {
|
|
const stepTypes = {
|
|
'start': { label: 'Process Started', icon: 'material-symbols:play-circle', color: 'green' },
|
|
'form': { label: 'User Form', icon: 'material-symbols:description', color: 'blue' },
|
|
'api': { label: 'API Call', icon: 'material-symbols:api', color: 'purple' },
|
|
'script': { label: 'Script Execution', icon: 'material-symbols:code', color: 'orange' },
|
|
'decision': { label: 'Decision Point', icon: 'material-symbols:alt-route', color: 'yellow' },
|
|
'end': { label: 'Process Complete', icon: 'material-symbols:check-circle', color: 'green' }
|
|
};
|
|
|
|
return stepTypes[node?.type] || { label: 'Unknown Step', icon: 'material-symbols:help', color: 'gray' };
|
|
};
|
|
|
|
// Load form data from database
|
|
const loadFormData = async (formId) => {
|
|
try {
|
|
if (!formId) return null;
|
|
|
|
const response = await $fetch(`/api/forms/${formId}`);
|
|
if (response.success) {
|
|
return response.form;
|
|
}
|
|
return null;
|
|
} catch (error) {
|
|
console.error('Error loading form:', error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Note: isInputType function removed as ComponentPreview handles all component types
|
|
|
|
// Get truncated custom script for display
|
|
const customScriptPreview = computed(() => {
|
|
if (!currentForm.value?.customScript) return '';
|
|
return currentForm.value.customScript.substring(0, 200) + '...';
|
|
});
|
|
|
|
// Handle script-driven field changes
|
|
const handleScriptFieldChange = ({ fieldName, value }) => {
|
|
console.log('[WorkflowExecution] Script field change:', fieldName, '=', value);
|
|
// Update form data with script changes
|
|
formData.value[fieldName] = value;
|
|
// Also update form store for ComponentPreview
|
|
formStore.updatePreviewFormData(formData.value);
|
|
};
|
|
|
|
// Handle conditional logic script generation
|
|
const handleConditionalLogicGenerated = (generatedScript) => {
|
|
console.log('[WorkflowExecution] Conditional logic script generated');
|
|
conditionalLogicScript.value = generatedScript;
|
|
|
|
// Combine conditional logic with custom script
|
|
const customScript = currentForm.value?.customScript || '';
|
|
combinedScript.value = [conditionalLogicScript.value, customScript].filter(Boolean).join('\n\n');
|
|
};
|
|
|
|
// Navigation functions
|
|
const goHome = () => {
|
|
router.push('/');
|
|
};
|
|
|
|
// Load process on mount
|
|
onMounted(() => {
|
|
loadProcess();
|
|
});
|
|
|
|
// Watch for step changes to auto-execute non-form steps or load form data
|
|
watch(currentStep, async (newStep) => {
|
|
if (currentNode.value) {
|
|
if (['api', 'script'].includes(currentNode.value.type)) {
|
|
await executeCurrentStep();
|
|
} else if (currentNode.value.type === 'form') {
|
|
// Load form data for form nodes
|
|
const formId = currentNode.value.data?.formId;
|
|
if (formId) {
|
|
currentForm.value = await loadFormData(formId);
|
|
// Update form store with form components and data for ComponentPreview
|
|
if (currentForm.value?.formComponents) {
|
|
formStore.formComponents = currentForm.value.formComponents;
|
|
formStore.updatePreviewFormData(formData.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Watch formData changes and sync with form store
|
|
watch(formData, (newData) => {
|
|
formStore.updatePreviewFormData(newData);
|
|
}, { deep: true });
|
|
|
|
// Add FormKit form ref
|
|
const formRef = ref(null);
|
|
|
|
// New: handle FormKit form submit
|
|
const onFormKitSubmit = () => {
|
|
handleFormSubmit();
|
|
};
|
|
|
|
// New: validate and submit handler for button
|
|
const validateAndSubmit = () => {
|
|
if (formRef.value && formRef.value.node && typeof formRef.value.node.submit === 'function') {
|
|
formRef.value.node.submit();
|
|
}
|
|
};
|
|
|
|
// Compute the workflow path by following edges from start to end
|
|
function computeWorkflowPath() {
|
|
const path = [];
|
|
const nodes = workflowData.value?.nodes || [];
|
|
const edges = workflowData.value?.edges || [];
|
|
if (!nodes.length) return path;
|
|
let node = nodes.find(n => n.type === 'start');
|
|
while (node) {
|
|
path.push(node.id);
|
|
const outgoingEdges = edges.filter(e => e.source === node.id);
|
|
if (outgoingEdges.length > 1) {
|
|
console.warn('[Workflow] Multiple outgoing edges found for node', node.id, outgoingEdges);
|
|
// For progress, just follow the first edge for now
|
|
}
|
|
const edge = outgoingEdges[0];
|
|
node = edge ? nodes.find(n => n.id === edge.target) : null;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
const workflowPath = ref([]);
|
|
|
|
watch(
|
|
() => workflowData.value,
|
|
() => {
|
|
workflowPath.value = computeWorkflowPath();
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const currentStepIndex = computed(() => {
|
|
return workflowPath.value.indexOf(currentNode.value?.id) + 1;
|
|
});
|
|
const totalSteps = computed(() => workflowPath.value.length);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen bg-gray-50">
|
|
<!-- Header -->
|
|
<header class="bg-white border-b border-gray-200 px-6 py-4 shadow-sm">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-4">
|
|
<Icon
|
|
@click="goHome"
|
|
name="ph:arrow-circle-left-duotone"
|
|
class="cursor-pointer w-6 h-6 hover:text-gray-600 text-gray-500"
|
|
/>
|
|
<div class="flex items-center gap-3">
|
|
<img
|
|
src="@/assets/img/logo/logo-word-black.svg"
|
|
alt="Corrad Logo"
|
|
class="h-8"
|
|
/>
|
|
<div class="border-l border-gray-300 pl-3">
|
|
<h1 class="text-xl font-semibold text-gray-900">Process Execution</h1>
|
|
<p class="text-sm text-gray-500">
|
|
{{ process?.processName || 'Loading...' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress indicator -->
|
|
<div v-if="!loading && !error" class="flex items-center gap-2 text-sm text-gray-600">
|
|
<span>Step {{ currentStepIndex }} of {{ totalSteps }}</span>
|
|
<div class="w-32 bg-gray-200 rounded-full h-2">
|
|
<div
|
|
class="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
|
:style="{ width: `${((currentStepIndex) / (totalSteps || 1)) * 100}%` }"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<div class="container mx-auto px-6 py-8 max-w-4xl">
|
|
<!-- Loading State -->
|
|
<div v-if="loading" class="flex justify-center items-center py-12">
|
|
<div class="text-center">
|
|
<Icon name="material-symbols:progress-activity" class="w-8 h-8 animate-spin text-blue-500 mx-auto mb-2" />
|
|
<p class="text-gray-500">Loading process...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else-if="error" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
|
<Icon name="material-symbols:error-outline" class="w-16 h-16 text-red-400 mx-auto mb-4" />
|
|
<h3 class="text-lg font-medium text-gray-900 mb-2">Error</h3>
|
|
<p class="text-gray-600 mb-6">{{ error }}</p>
|
|
<RsButton @click="goHome" variant="primary">
|
|
<Icon name="material-symbols:home" class="mr-2" />
|
|
Go Home
|
|
</RsButton>
|
|
</div>
|
|
|
|
<!-- Process Complete -->
|
|
<div v-else-if="isProcessComplete" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
|
<Icon name="material-symbols:check-circle" class="w-16 h-16 text-green-500 mx-auto mb-4" />
|
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">Process Complete!</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
The workflow "{{ process.processName }}" has been completed successfully.
|
|
</p>
|
|
<div class="flex justify-center gap-3">
|
|
<RsButton @click="loadProcess" variant="secondary">
|
|
<Icon name="material-symbols:refresh" class="mr-2" />
|
|
Run Again
|
|
</RsButton>
|
|
<RsButton @click="goHome" variant="primary">
|
|
<Icon name="material-symbols:home" class="mr-2" />
|
|
Go Home
|
|
</RsButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Step -->
|
|
<div v-else-if="currentNode" class="space-y-6">
|
|
<!-- Step Info -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
<div class="flex items-center gap-4 mb-4">
|
|
<div :class="[
|
|
'p-3 rounded-lg',
|
|
getStepInfo(currentNode).color === 'blue' ? 'bg-blue-100 text-blue-600' :
|
|
getStepInfo(currentNode).color === 'green' ? 'bg-green-100 text-green-600' :
|
|
getStepInfo(currentNode).color === 'purple' ? 'bg-purple-100 text-purple-600' :
|
|
getStepInfo(currentNode).color === 'orange' ? 'bg-orange-100 text-orange-600' :
|
|
'bg-gray-100 text-gray-600'
|
|
]">
|
|
<Icon :name="getStepInfo(currentNode).icon" class="w-6 h-6" />
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-semibold text-gray-900">{{ getStepInfo(currentNode).label }}</h2>
|
|
<p class="text-gray-600">{{ currentNode.data?.label || currentNode.data?.name || `Step ${currentStepIndex}` }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Step -->
|
|
<div v-if="currentNode.type === 'form'" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
<div class="mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
|
{{ currentNode.data?.formName || currentNode.data?.label || 'Please fill out the form' }}
|
|
</h3>
|
|
<p v-if="currentForm?.formDescription" class="text-gray-600 text-sm">
|
|
{{ currentForm.formDescription }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Loading form data -->
|
|
<div v-if="!currentForm && currentNode.data?.formId" class="flex justify-center py-8">
|
|
<div class="text-center">
|
|
<Icon name="material-symbols:progress-activity" class="w-6 h-6 animate-spin text-blue-500 mx-auto mb-2" />
|
|
<p class="text-gray-500">Loading form...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form rendering -->
|
|
<div v-else-if="currentForm && currentForm.formComponents" class="form-container">
|
|
<!-- Conditional Logic Engine for FormKit conditional logic -->
|
|
<ConditionalLogicEngine
|
|
:form-components="currentForm.formComponents"
|
|
:form-data="formData"
|
|
@script-generated="handleConditionalLogicGenerated"
|
|
/>
|
|
|
|
<!-- Form Script Engine for conditional logic + custom script -->
|
|
<FormScriptEngine
|
|
v-if="combinedScript"
|
|
:form-data="formData"
|
|
:custom-script="combinedScript"
|
|
:custom-css="currentForm.customCSS"
|
|
:form-events="currentForm.formEvents || { onLoad: true, onFieldChange: true }"
|
|
:script-mode="currentForm.scriptMode || 'safe'"
|
|
@field-change="handleScriptFieldChange"
|
|
/>
|
|
|
|
<!-- FormKit form wrapper -->
|
|
<FormKit
|
|
type="form"
|
|
v-model="formData"
|
|
ref="formRef"
|
|
@submit="onFormKitSubmit"
|
|
:actions="false"
|
|
:incomplete-message="false"
|
|
validation-visibility="submit"
|
|
>
|
|
<div class="space-y-6">
|
|
<template v-for="(component, index) in currentForm.formComponents" :key="index">
|
|
<ComponentPreview
|
|
:component="component"
|
|
:is-preview="false"
|
|
/>
|
|
</template>
|
|
<div class="flex justify-end pt-6 border-t border-gray-200">
|
|
<RsButton
|
|
@click="validateAndSubmit"
|
|
:disabled="stepLoading"
|
|
variant="primary"
|
|
>
|
|
<Icon v-if="stepLoading" name="material-symbols:progress-activity" class="w-4 h-4 animate-spin mr-2" />
|
|
{{ stepLoading ? 'Processing...' : 'Submit & Continue' }}
|
|
</RsButton>
|
|
</div>
|
|
</div>
|
|
</FormKit>
|
|
|
|
<!-- Script Info (for debugging) -->
|
|
<div v-if="combinedScript" class="mt-4 p-3 bg-gray-50 rounded-lg border">
|
|
<details>
|
|
<summary class="text-sm font-medium text-gray-700 cursor-pointer">Form Logic Available (Development)</summary>
|
|
<div class="mt-2 space-y-2">
|
|
<div v-if="conditionalLogicScript">
|
|
<p class="text-xs font-medium text-gray-600">Conditional Logic:</p>
|
|
<pre class="text-xs text-gray-600 bg-white p-2 rounded border overflow-auto max-h-32">{{ conditionalLogicScript.substring(0, 200) }}...</pre>
|
|
</div>
|
|
<div v-if="currentForm?.customScript">
|
|
<p class="text-xs font-medium text-gray-600">Custom Script:</p>
|
|
<pre class="text-xs text-gray-600 bg-white p-2 rounded border overflow-auto max-h-32">{{ customScriptPreview }}</pre>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fallback if no form data -->
|
|
<div v-else class="text-center py-8">
|
|
<Icon name="material-symbols:description-outline" class="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
|
<h4 class="text-lg font-medium text-gray-900 mb-2">Form Not Found</h4>
|
|
<p class="text-gray-600 mb-4">The form associated with this step could not be loaded.</p>
|
|
<RsButton @click="moveToNextStep" variant="primary">
|
|
<Icon name="material-symbols:skip-next" class="mr-2" />
|
|
Skip to Next Step
|
|
</RsButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API/Script Step -->
|
|
<div v-else-if="['api', 'script'].includes(currentNode.type)" class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
|
|
<Icon name="material-symbols:progress-activity" class="w-8 h-8 animate-spin text-blue-500 mx-auto mb-4" />
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
|
{{ currentNode.type === 'api' ? 'Calling API...' : 'Executing Script...' }}
|
|
</h3>
|
|
<p class="text-gray-600">Please wait while we process this step</p>
|
|
</div>
|
|
|
|
<!-- Other Step Types -->
|
|
<div v-else class="bg-white rounded-xl shadow-sm border border-gray-200 p-8 text-center">
|
|
<Icon name="material-symbols:info" class="w-8 h-8 text-blue-500 mx-auto mb-4" />
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">{{ getStepInfo(currentNode).label }}</h3>
|
|
<p class="text-gray-600 mb-6">This step type is not yet implemented</p>
|
|
<RsButton @click="moveToNextStep" variant="primary">
|
|
<Icon name="material-symbols:skip-next" class="mr-2" />
|
|
Skip to Next Step
|
|
</RsButton>
|
|
</div>
|
|
|
|
<!-- Process Variables Debug (only in development) -->
|
|
<div v-if="Object.keys(processVariables).length > 0" class="bg-gray-100 rounded-lg p-4">
|
|
<details>
|
|
<summary class="font-medium text-gray-700 cursor-pointer mb-2">Process Variables (Debug)</summary>
|
|
<pre class="text-xs text-gray-600 bg-white p-2 rounded border overflow-auto">{{ JSON.stringify(processVariables, null, 2) }}</pre>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.transition-all {
|
|
transition-property: all;
|
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
transition-duration: 300ms;
|
|
}
|
|
</style> |