Add Process Journey Timeline Component
- Introduced a new component, ProcessJourneyTimeline.vue, to visualize the journey of processes with interactive nodes and metrics. - Implemented props for journey data, display options for metrics, issues, and branches, and support for interactive node selection. - Enhanced user experience with dynamic styling based on node types and dropoff rates, providing visual feedback on process performance. - Added helper functions for formatting durations, calculating completion rates, and managing node interactions, ensuring a comprehensive overview of process journeys. - Updated styles for improved layout and responsiveness, aligning with existing design principles in the application.
This commit is contained in:
parent
35a0bd412e
commit
ba08d2e466
@ -1224,7 +1224,6 @@ function fromObject(flowObject) {
|
|||||||
/* Node styles from ProcessFlowNodes.js are imported globally in a plugin */
|
/* Node styles from ProcessFlowNodes.js are imported globally in a plugin */
|
||||||
.process-flow-container {
|
.process-flow-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - 190px); /* Adjust based on new header/footer height */
|
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
@ -121,11 +121,8 @@ const checkScreenSize = () => {
|
|||||||
showRightPanel.value = true;
|
showRightPanel.value = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Desktop: show both panels by default
|
// Desktop: show both panels by default only on first load
|
||||||
if (!showLeftPanel.value && !showRightPanel.value) {
|
// Don't override user preferences on screen size changes
|
||||||
showLeftPanel.value = true;
|
|
||||||
showRightPanel.value = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -148,6 +145,13 @@ const toggleRightPanel = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Toggle both panels at once
|
||||||
|
const toggleBothPanels = () => {
|
||||||
|
const bothVisible = showLeftPanel.value && showRightPanel.value;
|
||||||
|
showLeftPanel.value = !bothVisible;
|
||||||
|
showRightPanel.value = !bothVisible;
|
||||||
|
};
|
||||||
|
|
||||||
// Close panels when clicking on canvas (mobile only)
|
// Close panels when clicking on canvas (mobile only)
|
||||||
const onPaneClickMobile = () => {
|
const onPaneClickMobile = () => {
|
||||||
selectedNode.value = null;
|
selectedNode.value = null;
|
||||||
@ -162,6 +166,32 @@ const onPaneClickMobile = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keyboard shortcuts for panel toggles
|
||||||
|
const handleKeyboardShortcuts = (event) => {
|
||||||
|
// Only handle shortcuts when no input is focused
|
||||||
|
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl/Cmd + 1: Toggle left panel
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === '1') {
|
||||||
|
event.preventDefault();
|
||||||
|
toggleLeftPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl/Cmd + 2: Toggle right panel
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === '2') {
|
||||||
|
event.preventDefault();
|
||||||
|
toggleRightPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl/Cmd + 3: Toggle both panels
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === '3') {
|
||||||
|
event.preventDefault();
|
||||||
|
toggleBothPanels();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Handle node highlighting from variable usage
|
// Handle node highlighting from variable usage
|
||||||
const handleNodeHighlight = (event) => {
|
const handleNodeHighlight = (event) => {
|
||||||
|
|
||||||
@ -1111,6 +1141,9 @@ onMounted(() => {
|
|||||||
// Add node highlight listener for variable navigation
|
// Add node highlight listener for variable navigation
|
||||||
window.addEventListener('highlightNode', handleNodeHighlight);
|
window.addEventListener('highlightNode', handleNodeHighlight);
|
||||||
|
|
||||||
|
// Add keyboard shortcuts listener
|
||||||
|
window.addEventListener('keydown', handleKeyboardShortcuts);
|
||||||
|
|
||||||
// Initial screen size check
|
// Initial screen size check
|
||||||
checkScreenSize();
|
checkScreenSize();
|
||||||
});
|
});
|
||||||
@ -1128,6 +1161,7 @@ onUnmounted(() => {
|
|||||||
document.removeEventListener('click', handleClickOutside);
|
document.removeEventListener('click', handleClickOutside);
|
||||||
window.removeEventListener('resize', checkScreenSize);
|
window.removeEventListener('resize', checkScreenSize);
|
||||||
window.removeEventListener('highlightNode', handleNodeHighlight);
|
window.removeEventListener('highlightNode', handleNodeHighlight);
|
||||||
|
window.removeEventListener('keydown', handleKeyboardShortcuts);
|
||||||
|
|
||||||
// Clear highlight timeout if it exists
|
// Clear highlight timeout if it exists
|
||||||
if (highlightTimeout.value) {
|
if (highlightTimeout.value) {
|
||||||
@ -2107,31 +2141,57 @@ const sendToBack = () => {
|
|||||||
<p class="text-sm text-gray-500">Create business processes with drag and drop</p>
|
<p class="text-sm text-gray-500">Create business processes with drag and drop</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Mobile panel toggles -->
|
</div>
|
||||||
<div v-if="hasCurrentProcess && (isMobile || isTablet)" class="flex items-center gap-1 ml-2">
|
|
||||||
|
<!-- Middle section - Process name and Panel Controls -->
|
||||||
|
<div class="flex-1 flex justify-center items-center mx-2 md:mx-4 gap-4">
|
||||||
|
<!-- Panel toggle controls - Always visible when process is loaded -->
|
||||||
|
<div v-if="hasCurrentProcess" class="flex items-center gap-1">
|
||||||
<RsButton
|
<RsButton
|
||||||
@click="toggleLeftPanel"
|
@click="toggleLeftPanel"
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
size="sm"
|
size="sm"
|
||||||
:class="{ 'bg-gray-100': showLeftPanel }"
|
:class="{
|
||||||
class="p-1"
|
'bg-blue-100 text-blue-600 border-blue-200': showLeftPanel,
|
||||||
|
'bg-gray-100 text-gray-600 border-gray-200': !showLeftPanel
|
||||||
|
}"
|
||||||
|
class="p-2 border transition-all duration-200 hover:scale-105 relative"
|
||||||
|
title="Toggle Components Panel (Ctrl+1)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:widgets" class="w-4 h-4" />
|
<Icon name="material-symbols:widgets" class="w-4 h-4" />
|
||||||
|
<span v-if="!showLeftPanel" class="absolute -top-1 -right-1 w-2 h-2 bg-orange-400 rounded-full"></span>
|
||||||
</RsButton>
|
</RsButton>
|
||||||
<RsButton
|
<RsButton
|
||||||
@click="toggleRightPanel"
|
@click="toggleRightPanel"
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
size="sm"
|
size="sm"
|
||||||
:class="{ 'bg-gray-100': showRightPanel }"
|
:class="{
|
||||||
class="p-1"
|
'bg-blue-100 text-blue-600 border-blue-200': showRightPanel,
|
||||||
|
'bg-gray-100 text-gray-600 border-gray-200': !showRightPanel
|
||||||
|
}"
|
||||||
|
class="p-2 border transition-all duration-200 hover:scale-105 relative"
|
||||||
|
title="Toggle Properties Panel (Ctrl+2)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:tune" class="w-4 h-4" />
|
<Icon name="material-symbols:tune" class="w-4 h-4" />
|
||||||
|
<span v-if="!showRightPanel" class="absolute -top-1 -right-1 w-2 h-2 bg-orange-400 rounded-full"></span>
|
||||||
|
</RsButton>
|
||||||
|
<RsButton
|
||||||
|
@click="toggleBothPanels"
|
||||||
|
variant="tertiary"
|
||||||
|
size="sm"
|
||||||
|
:class="{
|
||||||
|
'bg-blue-100 text-blue-600 border-blue-200': showLeftPanel && showRightPanel,
|
||||||
|
'bg-gray-100 text-gray-600 border-gray-200': !(showLeftPanel && showRightPanel)
|
||||||
|
}"
|
||||||
|
class="p-2 hidden md:block border transition-all duration-200 hover:scale-105 relative"
|
||||||
|
title="Toggle Both Panels (Ctrl+3)"
|
||||||
|
>
|
||||||
|
<Icon name="material-symbols:view-sidebar" class="w-4 h-4" />
|
||||||
|
<span v-if="!showLeftPanel || !showRightPanel" class="absolute -top-1 -right-1 w-2 h-2 bg-orange-400 rounded-full"></span>
|
||||||
</RsButton>
|
</RsButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Middle section - Process name -->
|
<!-- Process name input -->
|
||||||
<div class="flex-1 flex justify-center items-center mx-2 md:mx-4">
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="hasCurrentProcess"
|
v-if="hasCurrentProcess"
|
||||||
v-model="processStore.currentProcess.name"
|
v-model="processStore.currentProcess.name"
|
||||||
@ -2209,14 +2269,14 @@ const sendToBack = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Main Content Area -->
|
<!-- Main Content Area -->
|
||||||
<div class="flex-1 flex overflow-hidden relative" v-if="hasCurrentProcess">
|
<div class="flex-1 flex overflow-hidden relative h-full" v-if="hasCurrentProcess">
|
||||||
<!-- Left Panel - Components -->
|
<!-- Left Panel - Components -->
|
||||||
<div
|
<div
|
||||||
v-show="showLeftPanel"
|
v-show="showLeftPanel"
|
||||||
:class="{
|
:class="{
|
||||||
'absolute inset-y-0 left-0 z-20 bg-white shadow-lg': isMobile,
|
'absolute inset-y-0 left-0 z-20 bg-white shadow-lg': isMobile,
|
||||||
'absolute inset-y-0 left-0 z-10 bg-white shadow-md': isTablet,
|
'absolute inset-y-0 left-0 z-10 bg-white shadow-md': isTablet,
|
||||||
'relative w-64': !isMobile && !isTablet,
|
'relative w-60': !isMobile && !isTablet,
|
||||||
'w-72': isMobile,
|
'w-72': isMobile,
|
||||||
'w-80': isTablet
|
'w-80': isTablet
|
||||||
}"
|
}"
|
||||||
@ -2224,11 +2284,11 @@ const sendToBack = () => {
|
|||||||
>
|
>
|
||||||
<div class="bg-gray-100 p-3 flex items-center justify-between border-b border-gray-200">
|
<div class="bg-gray-100 p-3 flex items-center justify-between border-b border-gray-200">
|
||||||
<h2 class="text-sm font-medium text-gray-700">Process Components</h2>
|
<h2 class="text-sm font-medium text-gray-700">Process Components</h2>
|
||||||
<!-- Close button for mobile/tablet -->
|
<!-- Close button for all devices -->
|
||||||
<button
|
<button
|
||||||
v-if="isMobile || isTablet"
|
|
||||||
@click="showLeftPanel = false"
|
@click="showLeftPanel = false"
|
||||||
class="p-1 hover:bg-gray-200 rounded"
|
class="p-1 hover:bg-gray-200 rounded transition-colors"
|
||||||
|
title="Close Panel (Ctrl+1)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:close" class="w-4 h-4 text-gray-500" />
|
<Icon name="material-symbols:close" class="w-4 h-4 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
@ -2239,7 +2299,16 @@ const sendToBack = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Center Panel - Process Canvas -->
|
<!-- Center Panel - Process Canvas -->
|
||||||
<div class="flex-1 relative">
|
<div class="flex-1 relative h-full" :class="{ 'bg-gray-50': !showLeftPanel && !showRightPanel }">
|
||||||
|
<!-- Canvas State Indicator -->
|
||||||
|
<div v-if="!showLeftPanel && !showRightPanel" class="absolute top-4 left-1/2 transform -translate-x-1/2 z-10 bg-white px-4 py-2 rounded-lg shadow-md border border-gray-200">
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<Icon name="material-symbols:fullscreen" class="w-4 h-4 text-green-600" />
|
||||||
|
<span>Full Canvas Mode</span>
|
||||||
|
<span class="text-xs text-gray-400">Press Ctrl+3 to restore panels</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ProcessFlowCanvas
|
<ProcessFlowCanvas
|
||||||
ref="processFlowCanvas"
|
ref="processFlowCanvas"
|
||||||
:initial-nodes="canvasNodes"
|
:initial-nodes="canvasNodes"
|
||||||
@ -2250,6 +2319,7 @@ const sendToBack = () => {
|
|||||||
@pane-click="onPaneClick"
|
@pane-click="onPaneClick"
|
||||||
@nodes-change="onNodesChange"
|
@nodes-change="onNodesChange"
|
||||||
@edges-change="onEdgesChange"
|
@edges-change="onEdgesChange"
|
||||||
|
class="w-full h-full"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Mobile floating action buttons -->
|
<!-- Mobile floating action buttons -->
|
||||||
@ -2279,7 +2349,7 @@ const sendToBack = () => {
|
|||||||
:class="{
|
:class="{
|
||||||
'absolute inset-y-0 right-0 z-20 bg-white shadow-lg': isMobile,
|
'absolute inset-y-0 right-0 z-20 bg-white shadow-lg': isMobile,
|
||||||
'absolute inset-y-0 right-0 z-10 bg-white shadow-md': isTablet,
|
'absolute inset-y-0 right-0 z-10 bg-white shadow-md': isTablet,
|
||||||
'relative w-72': !isMobile && !isTablet,
|
'relative w-64': !isMobile && !isTablet,
|
||||||
'w-72': isMobile,
|
'w-72': isMobile,
|
||||||
'w-80': isTablet
|
'w-80': isTablet
|
||||||
}"
|
}"
|
||||||
@ -2287,11 +2357,11 @@ const sendToBack = () => {
|
|||||||
>
|
>
|
||||||
<div class="bg-gray-100 p-3 flex items-center justify-between border-b border-gray-200">
|
<div class="bg-gray-100 p-3 flex items-center justify-between border-b border-gray-200">
|
||||||
<h2 class="text-sm font-medium text-gray-700">Properties</h2>
|
<h2 class="text-sm font-medium text-gray-700">Properties</h2>
|
||||||
<!-- Close button for mobile/tablet -->
|
<!-- Close button for all devices -->
|
||||||
<button
|
<button
|
||||||
v-if="isMobile || isTablet"
|
|
||||||
@click="showRightPanel = false"
|
@click="showRightPanel = false"
|
||||||
class="p-1 hover:bg-gray-200 rounded"
|
class="p-1 hover:bg-gray-200 rounded transition-colors"
|
||||||
|
title="Close Panel (Ctrl+2)"
|
||||||
>
|
>
|
||||||
<Icon name="material-symbols:close" class="w-4 h-4 text-gray-500" />
|
<Icon name="material-symbols:close" class="w-4 h-4 text-gray-500" />
|
||||||
</button>
|
</button>
|
||||||
@ -3825,4 +3895,27 @@ const sendToBack = () => {
|
|||||||
.modern-icon-btn[variant~='danger-text']:hover {
|
.modern-icon-btn[variant~='danger-text']:hover {
|
||||||
background: #fee2e2;
|
background: #fee2e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure full canvas coverage */
|
||||||
|
.process-builder {
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override Vue Flow container to fill full height */
|
||||||
|
:deep(.vue-flow) {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
min-height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vue-flow__pane) {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.vue-flow__renderer) {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user