diff --git a/components/process-flow/HtmlNodeConfiguration.vue b/components/process-flow/HtmlNodeConfiguration.vue
new file mode 100644
index 0000000..bf98eb7
--- /dev/null
+++ b/components/process-flow/HtmlNodeConfiguration.vue
@@ -0,0 +1,700 @@
+
+
+
+
+
+
+
+
+
+
+
+
HTML Content
+
+
+
+
+
+
+
+
+
+
+
+ ๐ก You can use variables in your HTML with double curly braces: {{ variableSyntaxExample }}
+
+
+
+
+
+
+
+
+
+ ๐ก Add CSS to style your HTML content. These styles will be scoped to this HTML node only.
+
+
+
+
+
+
+
+
+
+ ๐ก Use processVariables
to access and modify process data.
+ Available variables: {{ availableVariableNames.join(', ') || 'None' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Variable Management
+
+
+
+
+
+ Input Variables
+
+
+
+
+ {{ variable.name }}
+ {{ variable.type }}
+
+
{{ variable.description }}
+
+
+
+ Click to select variables this HTML node will read from
+
+
+
+
+
+
+ Output Variables
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Output Variable
+
+
+
+ Define variables this HTML node will create or modify
+
+
+
+
+
+
+
+
+
Preview
+
+
+
+
HTML Preview
+
+ Auto-refresh
+
+
+
+
+
+
+ Refresh Preview
+
+
+
+
+
+
+
+
+
Click "Refresh Preview" to see your HTML content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/process-flow/HtmlNodeConfigurationModal.vue b/components/process-flow/HtmlNodeConfigurationModal.vue
new file mode 100644
index 0000000..7ba6338
--- /dev/null
+++ b/components/process-flow/HtmlNodeConfigurationModal.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
Configure HTML Node
+
+ Create custom HTML content to display in your process flow.
+ HTML nodes are useful for creating custom forms, displaying information, and providing interactive elements.
+
+
+
+
+
+
+
+
+
+
+
+
+ Quick Reference Guide
+
+
+
+ Use {{ variableSyntaxExample }}
syntax to display process variables in your HTML
+ Add CSS styles to customize the appearance of your HTML content
+ Include JavaScript for interactive functionality
+ Select input variables that your HTML node will read from
+ Define output variables that your HTML node will modify
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/process-flow/ProcessBuilderComponents.vue b/components/process-flow/ProcessBuilderComponents.vue
index 29ef021..2b6cb0f 100644
--- a/components/process-flow/ProcessBuilderComponents.vue
+++ b/components/process-flow/ProcessBuilderComponents.vue
@@ -198,13 +198,46 @@ const availableComponents = [
defaultProps: {
label: 'Script Task',
data: {
- description: 'Data transformation script',
- scriptCode: '// Transform API response or process variables\n// Example:\n// processVariables.newVariable = processVariables.apiResponse.data.field;\n',
+ description: 'Execute JavaScript code',
+ scriptCode: '',
scriptLanguage: 'javascript',
inputVariables: [],
+ outputVariables: []
+ }
+ }
+ },
+ {
+ type: 'html',
+ name: 'HTML Content',
+ category: 'Core',
+ icon: 'material-symbols:code',
+ description: 'Display custom HTML content',
+ defaultProps: {
+ label: 'HTML Content',
+ data: {
+ description: 'Custom HTML content',
+ htmlCode: '\n
\n
Custom HTML Content \n
This is a custom HTML node that can display rich content.
\n
',
+ cssCode: '',
+ jsCode: '',
+ inputVariables: [],
outputVariables: [],
- continueOnError: false,
- errorVariable: 'scriptError'
+ allowVariableAccess: true,
+ autoRefresh: false
+ }
+ }
+ },
+ {
+ type: 'subprocess',
+ name: 'Sub Process',
+ category: 'Core',
+ icon: 'material-symbols:hub-outline',
+ description: 'Execute another process as a sub-process',
+ defaultProps: {
+ label: 'Sub Process',
+ data: {
+ description: 'Executes another process',
+ subprocessId: null,
+ subprocessName: ''
}
}
},
@@ -289,66 +322,6 @@ const availableComponents = [
shapeType: 'text-annotation'
}
}
- },
- {
- type: 'process-group',
- name: 'Process Group',
- category: 'Shape',
- icon: 'material-symbols:group-work',
- description: 'Group container for organizing related processes',
- defaultProps: {
- label: '',
- data: {
- description: '',
- width: 400,
- height: 300,
- backgroundColor: '#f0f9ff',
- borderColor: '#0284c7',
- textColor: '#0369a1',
- isShape: true,
- shapeType: 'process-group'
- }
- }
- },
- {
- type: 'hexagon-shape',
- name: 'Hexagon',
- category: 'Shape',
- icon: 'material-symbols:hexagon',
- description: 'Hexagon shape for special processes or decision points',
- defaultProps: {
- label: '',
- data: {
- description: '',
- width: 200,
- height: 150,
- backgroundColor: '#f8fafc',
- borderColor: '#e2e8f0',
- textColor: '#475569',
- isShape: true,
- shapeType: 'hexagon'
- }
- }
- },
- {
- type: 'trapezoid-shape',
- name: 'Trapezoid',
- category: 'Shape',
- icon: 'material-symbols:change-history',
- description: 'Trapezoid shape for data processing or manual tasks',
- defaultProps: {
- label: '',
- data: {
- description: '',
- width: 220,
- height: 120,
- backgroundColor: '#f8fafc',
- borderColor: '#e2e8f0',
- textColor: '#475569',
- isShape: true,
- shapeType: 'trapezoid'
- }
- }
}
];
@@ -389,6 +362,7 @@ const onDragStart = (event, component) => {
// Add a component directly via click
const addComponent = (component) => {
+ return;
// Use same format as drag operation for consistency
const componentData = {
type: component.type,
diff --git a/components/process-flow/ProcessFlowCanvas.vue b/components/process-flow/ProcessFlowCanvas.vue
index 90e56d2..1dc78e4 100644
--- a/components/process-flow/ProcessFlowCanvas.vue
+++ b/components/process-flow/ProcessFlowCanvas.vue
@@ -334,7 +334,6 @@ watch(() => props.initialNodes, async (newNodes, oldNodes) => {
isUpdatingNodes.value = true;
try {
- // console.log('ProcessFlowCanvas: Updating nodes, count:', newNodes.length);
// Instead of clearing all nodes, sync them intelligently
const currentNodeIds = new Set(nodes.value.map(n => n.id));
@@ -393,15 +392,37 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
isUpdatingEdges.value = true;
try {
- // console.log('ProcessFlowCanvas: Updating edges, count:', newEdges.length, 'nodeCount:', nodeCount);
// Instead of clearing all edges, sync them intelligently
const currentEdgeIds = new Set(edges.value.map(e => e.id));
const newEdgeIds = new Set(newEdges.map(e => e.id));
- // Remove edges that are no longer in the new list
- const edgesToRemove = edges.value.filter(edge => !newEdgeIds.has(edge.id));
+
+ // CRITICAL: Be more conservative about edge removal
+ // Only remove edges that are definitely not in the new list AND whose nodes don't exist
+ const edgesToRemove = edges.value.filter(edge => {
+ const isInNewList = newEdgeIds.has(edge.id);
+
+ if (isInNewList) {
+ return false; // Don't remove if it's in the new list
+ }
+
+ // Double-check that both source and target nodes still exist
+ const sourceExists = nodes.value.some(node => node.id === edge.source);
+ const targetExists = nodes.value.some(node => node.id === edge.target);
+
+ // Only remove if nodes don't exist (orphaned edges)
+ const shouldRemove = !sourceExists || !targetExists;
+
+ if (shouldRemove) {
+ console.log(`๐๏ธ Removing orphaned edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`);
+ }
+
+ return shouldRemove;
+ });
+
if (edgesToRemove.length > 0) {
+ console.log('๐๏ธ Removing edges:', edgesToRemove.map(e => `${e.source}->${e.target} (${e.id})`));
removeEdges(edgesToRemove);
}
@@ -412,7 +433,7 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
const targetExists = nodes.value.some(node => node.id === edge.target);
if (!sourceExists || !targetExists) {
- console.warn(`Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`);
+ console.warn(`โ ๏ธ Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`);
return false;
}
@@ -423,10 +444,13 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
const edgesToAdd = validEdges.filter(edge => !currentEdgeIds.has(edge.id));
if (edgesToAdd.length > 0) {
+ console.log('โ Adding new edges:', edgesToAdd.map(e => `${e.source}->${e.target} (${e.id})`));
+
// Ensure all edges have proper handle specifications
const edgesWithHandles = edgesToAdd.map(edge => {
// IMPORTANT: If edge already has sourceHandle and targetHandle, preserve them exactly as they are
if (edge.sourceHandle && edge.targetHandle) {
+ console.log(`๐ Edge ${edge.id} already has handles: ${edge.sourceHandle} -> ${edge.targetHandle}`);
return edge;
}
@@ -457,6 +481,7 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
}
}
+ console.log(`๐ Generated handles for edge ${edge.id}: ${sourceHandle} -> ${targetHandle}`);
return {
...edge,
sourceHandle,
@@ -465,10 +490,10 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
});
addEdges([...edgesWithHandles]); // Create a copy to avoid reactivity issues
- // console.log('ProcessFlowCanvas: Successfully added edges with handles:', edgesWithHandles.length);
}
// Update existing edges that have changed - IMPORTANT: preserve handle positions
+ let updatedEdgeCount = 0;
newEdges.forEach(newEdge => {
const existingEdge = edges.value.find(e => e.id === newEdge.id);
if (existingEdge) {
@@ -481,15 +506,21 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
if (hasChanges) {
Object.assign(existingEdge, {
label: newEdge.label,
- // Preserve existing handles if they exist
+ // CRITICAL: Preserve existing handles if they exist
sourceHandle: existingEdge.sourceHandle || newEdge.sourceHandle,
targetHandle: existingEdge.targetHandle || newEdge.targetHandle,
style: newEdge.style ? { ...newEdge.style } : undefined
});
+ updatedEdgeCount++;
}
}
});
+
+ if (updatedEdgeCount > 0) {
+ console.log('๐ Updated existing edges:', updatedEdgeCount);
+ }
}
+
} finally {
// Use a small delay to prevent immediate re-triggering
setTimeout(() => {
@@ -497,7 +528,7 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
}, 50);
}
} else if (newEdges && Array.isArray(newEdges) && newEdges.length > 0 && nodeCount === 0) {
- // console.log('ProcessFlowCanvas: Edges provided but no nodes yet, waiting...');
+ console.log('โ ๏ธ ProcessFlowCanvas: Edges provided but no nodes yet, waiting...');
}
}, { deep: true });
@@ -723,30 +754,31 @@ function removeNode(nodeId) {
return nodeToRemove;
}
-// Add explicit sync method to manually update canvas
+// Manual sync function for explicit canvas updates
function syncCanvas(newNodes, newEdges) {
- // Force clear the updating flags first to ensure we can process
- isUpdatingNodes.value = false;
- isUpdatingEdges.value = false;
+ console.log('๐ Manual canvas sync requested - nodes:', newNodes?.length || 0, 'edges:', newEdges?.length || 0);
- // Wait a moment for any ongoing operations to complete
- setTimeout(() => {
+ // Use a small delay to ensure any pending Vue Flow operations complete
+ setTimeout(async () => {
try {
-
// Sync nodes first
if (newNodes && Array.isArray(newNodes)) {
const currentNodeIds = new Set(nodes.value.map(n => n.id));
const newNodeIds = new Set(newNodes.map(n => n.id));
+ console.log('๐ Current canvas nodes:', currentNodeIds.size, 'New nodes:', newNodeIds.size);
+
// Remove nodes that are no longer in the new list
const nodesToRemove = nodes.value.filter(node => !newNodeIds.has(node.id));
if (nodesToRemove.length > 0) {
+ console.log('๐๏ธ Removing nodes:', nodesToRemove.map(n => n.id));
removeNodes(nodesToRemove);
}
// Add new nodes that aren't already present
const nodesToAdd = newNodes.filter(node => !currentNodeIds.has(node.id));
if (nodesToAdd.length > 0) {
+ console.log('โ Adding new nodes:', nodesToAdd.map(n => n.id));
addNodes([...nodesToAdd]);
}
@@ -764,28 +796,45 @@ function syncCanvas(newNodes, newEdges) {
updateNodeInternals([newNode.id]);
}
});
+
+ // Wait for nodes to be fully processed before handling edges
+ await nextTick();
+ await new Promise(resolve => setTimeout(resolve, 100));
}
-
+ console.log('๐ Canvas state after node sync - nodes:', nodes.value.length, 'edges:', edges.value.length);
- // Sync edges after nodes are updated
+ // Sync edges after nodes are updated - CRITICAL: Only if we have nodes
if (newEdges && Array.isArray(newEdges) && nodes.value.length > 0) {
const currentEdgeIds = new Set(edges.value.map(e => e.id));
const newEdgeIds = new Set(newEdges.map(e => e.id));
-
+ console.log('๐ Current canvas edges:', currentEdgeIds.size, 'New edges:', newEdgeIds.size);
+
+ // CRITICAL: Only remove edges that are definitely not in the new list
+ // Be more conservative about edge removal to prevent accidental deletions
+ const edgesToRemove = edges.value.filter(edge => {
+ const shouldRemove = !newEdgeIds.has(edge.id);
+ if (shouldRemove) {
+ // Double-check that both source and target nodes still exist
+ const sourceExists = nodes.value.some(node => node.id === edge.source);
+ const targetExists = nodes.value.some(node => node.id === edge.target);
+
+ // Only remove if the edge is truly not needed OR if nodes don't exist
+ return !sourceExists || !targetExists;
+ }
+ return false;
+ });
- // Remove edges that are no longer in the new list
- const edgesToRemove = edges.value.filter(edge => !newEdgeIds.has(edge.id));
if (edgesToRemove.length > 0) {
-
+ console.log('๐๏ธ Removing edges:', edgesToRemove.map(e => `${e.source}->${e.target} (${e.id})`));
removeEdges(edgesToRemove);
}
// Add new edges that aren't already present
const edgesToAdd = newEdges.filter(edge => !currentEdgeIds.has(edge.id));
if (edgesToAdd.length > 0) {
-
+ console.log('โ Adding new edges:', edgesToAdd.map(e => `${e.source}->${e.target} (${e.id})`));
// Verify nodes exist and add handles
const validEdges = edgesToAdd.filter(edge => {
@@ -793,19 +842,19 @@ function syncCanvas(newNodes, newEdges) {
const targetExists = nodes.value.some(node => node.id === edge.target);
if (!sourceExists || !targetExists) {
- console.warn(`Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`);
+ console.warn(`โ ๏ธ Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`);
return false;
}
return true;
});
-
+ console.log('โ
Valid edges to add:', validEdges.length);
const edgesWithHandles = validEdges.map(edge => {
// If edge already has sourceHandle and targetHandle, use them
if (edge.sourceHandle && edge.targetHandle) {
-
+ console.log(`๐ Edge ${edge.id} already has handles: ${edge.sourceHandle} -> ${edge.targetHandle}`);
return edge;
}
@@ -833,36 +882,51 @@ function syncCanvas(newNodes, newEdges) {
}
}
-
+ console.log(`๐ Generated handles for edge ${edge.id}: ${sourceHandle} -> ${targetHandle}`);
return { ...edge, sourceHandle, targetHandle };
});
if (edgesWithHandles.length > 0) {
-
+ console.log('โ
Adding edges with handles:', edgesWithHandles.length);
addEdges([...edgesWithHandles]);
}
}
- // Update existing edges
+ // Update existing edges - preserve handles and only update changed properties
+ let updatedEdgeCount = 0;
newEdges.forEach(newEdge => {
const existingEdge = edges.value.find(e => e.id === newEdge.id);
if (existingEdge) {
- Object.assign(existingEdge, {
- label: newEdge.label,
- sourceHandle: newEdge.sourceHandle,
- targetHandle: newEdge.targetHandle,
- style: newEdge.style ? { ...newEdge.style } : undefined
- });
+ // Check if update is needed
+ const needsUpdate = (
+ existingEdge.label !== newEdge.label ||
+ JSON.stringify(existingEdge.style) !== JSON.stringify(newEdge.style)
+ );
+
+ if (needsUpdate) {
+ Object.assign(existingEdge, {
+ label: newEdge.label,
+ // CRITICAL: Preserve existing handles if they exist
+ sourceHandle: existingEdge.sourceHandle || newEdge.sourceHandle,
+ targetHandle: existingEdge.targetHandle || newEdge.targetHandle,
+ style: newEdge.style ? { ...newEdge.style } : undefined
+ });
+ updatedEdgeCount++;
+ }
}
});
+
+ if (updatedEdgeCount > 0) {
+ console.log('๐ Updated existing edges:', updatedEdgeCount);
+ }
} else if (newEdges && Array.isArray(newEdges) && newEdges.length > 0) {
- console.warn('Cannot add edges: nodes not ready. Node count:', nodes.value.length);
+ console.warn('โ ๏ธ Cannot add edges: nodes not ready. Node count:', nodes.value.length);
}
-
+ console.log('โ
Canvas sync completed - final state: nodes:', nodes.value.length, 'edges:', edges.value.length);
} catch (error) {
- console.error('Error during canvas sync:', error);
+ console.error('โ Error during canvas sync:', error);
}
}, 50); // Small delay to allow any pending operations to complete
}
@@ -1187,6 +1251,10 @@ function fromObject(flowObject) {
color: #607D8B;
}
+:deep(.node-html .custom-node-icon .material-icons) {
+ color: #0ea5e9;
+}
+
:deep(.custom-node-title) {
font-weight: 500;
flex-grow: 1;
diff --git a/components/process-flow/ProcessFlowNodes.js b/components/process-flow/ProcessFlowNodes.js
index cd82aea..23ccb73 100644
--- a/components/process-flow/ProcessFlowNodes.js
+++ b/components/process-flow/ProcessFlowNodes.js
@@ -144,6 +144,11 @@ const CustomNode = markRaw({
defaultBorder = '#0ea5e9';
defaultText = '#0284c7';
break;
+ case 'subprocess':
+ defaultBg = '#f0fdfa';
+ defaultBorder = '#14b8a6';
+ defaultText = '#134e4a';
+ break;
}
}
@@ -444,6 +449,67 @@ export const ScriptNode = markRaw({
}
});
+// HTML node
+export const HtmlNode = markRaw({
+ props: ['id', 'type', 'label', 'selected', 'data'],
+ computed: {
+ nodeLabel() {
+ // Get label from either prop or data, with fallback
+ return this.label || (this.data && this.data.label) || 'HTML Content';
+ },
+ hasHtmlContent() {
+ return !!this.data?.htmlCode;
+ },
+ hasCssContent() {
+ return !!this.data?.cssCode;
+ },
+ hasJsContent() {
+ return !!this.data?.jsCode;
+ },
+ contentSummary() {
+ const parts = [];
+ if (this.hasHtmlContent) parts.push('HTML');
+ if (this.hasCssContent) parts.push('CSS');
+ if (this.hasJsContent) parts.push('JS');
+
+ return parts.length > 0 ? parts.join(' + ') : 'Empty';
+ }
+ },
+ render() {
+ return h(CustomNode, {
+ id: this.id,
+ type: 'html',
+ label: this.nodeLabel,
+ selected: this.selected,
+ data: this.data,
+ onClick: () => this.$emit('node-click', this.id)
+ }, {
+ icon: () => h('i', { class: 'material-icons text-blue-500' }, 'code'),
+ default: () => h('div', { class: 'node-details' }, [
+ h('p', { class: 'node-description' }, this.data?.description || 'Custom HTML content'),
+ h('div', { class: 'node-rule-detail flex items-center justify-between text-xs mt-1' }, [
+ h('span', { class: 'node-rule-detail-label' }, 'Content:'),
+ h('span', {
+ class: 'node-rule-detail-value ml-1 font-medium text-blue-600'
+ }, 'HTML')
+ ]),
+ h('div', { class: 'node-rule-detail flex items-center justify-between text-xs mt-1' }, [
+ h('span', { class: 'node-rule-detail-label' }, 'Status:'),
+ h('span', {
+ class: 'node-rule-detail-value ml-1 font-medium text-gray-600'
+ }, this.hasHtmlContent ? 'Configured' : 'Not configured')
+ ]),
+ h('div', { class: 'node-rule-detail flex items-center justify-between text-xs mt-1' }, [
+ h('span', { class: 'node-rule-detail-label' }, 'Variables:'),
+ h('span', {
+ class: 'node-rule-detail-value ml-1 font-medium text-blue-600'
+ }, this.data?.allowVariableAccess ? 'Enabled' : 'Disabled')
+ ])
+ ])
+ });
+ }
+});
+
// API Call node
export const ApiCallNode = markRaw({
props: ['id', 'type', 'label', 'selected', 'data'],
@@ -650,8 +716,52 @@ export const NotificationNode = markRaw({
}
});
+// Subprocess node
+export const SubprocessNode = markRaw({
+ props: ['id', 'type', 'label', 'selected', 'data'],
+ computed: {
+ nodeLabel() {
+ return this.label || (this.data && this.data.label) || 'Sub Process';
+ },
+ subprocessName() {
+ return this.data?.subprocessName || 'None selected';
+ },
+ isConfigured() {
+ return !!this.data?.subprocessId;
+ }
+ },
+ render() {
+ return h(CustomNode, {
+ id: this.id,
+ type: 'subprocess',
+ label: this.nodeLabel,
+ selected: this.selected,
+ data: this.data,
+ onClick: () => this.$emit('node-click', this.id)
+ }, {
+ icon: () => h('i', { class: 'material-icons text-teal-500' }, 'hub'),
+ default: () => h('div', { class: 'node-details' }, [
+ h('p', { class: 'node-description' }, this.data?.description || 'Executes another process'),
+ h('div', { class: 'node-rule-detail flex items-center justify-between text-xs mt-1' }, [
+ h('span', { class: 'node-rule-detail-label' }, 'Process:'),
+ h('span', {
+ class: this.isConfigured ? 'node-rule-detail-value ml-1 font-medium text-teal-600' : 'node-rule-detail-value ml-1 italic text-gray-400'
+ }, this.subprocessName)
+ ]),
+ h('div', { class: 'node-rule-detail flex items-center justify-between text-xs mt-1' }, [
+ h('span', { class: 'node-rule-detail-label' }, 'Status:'),
+ h('span', {
+ class: 'node-rule-detail-value ml-1 font-medium',
+ 'class': this.isConfigured ? 'text-green-600' : 'text-red-600'
+ }, this.isConfigured ? 'Configured' : 'Not configured')
+ ])
+ ])
+ });
+ }
+});
+
// Shape Components (Design Elements)
-const HorizontalSwimlaneShape = {
+const HorizontalSwimlaneShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -693,9 +803,9 @@ const HorizontalSwimlaneShape = {
`
-};
+});
-const VerticalSwimlaneShape = {
+const VerticalSwimlaneShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -739,9 +849,9 @@ const VerticalSwimlaneShape = {
`
-};
+});
-const RectangleShape = {
+const RectangleShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -783,9 +893,9 @@ const RectangleShape = {
`
-};
+});
-const TextAnnotationShape = {
+const TextAnnotationShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -828,9 +938,9 @@ const TextAnnotationShape = {
`
-};
+});
-const ProcessGroupShape = {
+const ProcessGroupShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -873,10 +983,10 @@ const ProcessGroupShape = {
`
-};
+});
// Hexagon Shape Component
-const HexagonShape = {
+const HexagonShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -909,10 +1019,10 @@ const HexagonShape = {
`
-};
+});
// Trapezoid Shape Component
-const TrapezoidShape = {
+const TrapezoidShape = markRaw({
props: ['id', 'data', 'selected', 'label'],
computed: {
shapeStyle() {
@@ -945,7 +1055,7 @@ const TrapezoidShape = {
`
-};
+});
// Export the node types object to use with Vue Flow
export const nodeTypes = {
@@ -957,6 +1067,8 @@ export const nodeTypes = {
'business-rule': BusinessRuleNode,
api: ApiCallNode,
notification: NotificationNode,
+ html: HtmlNode, // Add the new HtmlNode to the nodeTypes object
+ subprocess: SubprocessNode,
// Shape nodes
'swimlane-horizontal': HorizontalSwimlaneShape,
'swimlane-vertical': VerticalSwimlaneShape,
@@ -1586,6 +1698,14 @@ export const nodeStyles = `
border-left: 4px solid #6b7280; /* Gray border to match icon color */
}
+/* HTML node styling */
+.node-html {
+ border-left: 4px solid #0ea5e9; /* Blue border to match icon color */
+ background-color: #f0f9ff;
+ border-radius: 4px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+}
+
/* Shape node styles */
.shape-node {
position: relative;
diff --git a/components/process-flow/SubprocessNodeConfiguration.vue b/components/process-flow/SubprocessNodeConfiguration.vue
new file mode 100644
index 0000000..73e8aaf
--- /dev/null
+++ b/components/process-flow/SubprocessNodeConfiguration.vue
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
Available Processes
+
+
+
+
+
+
+
+ Loading...
+
+
Error loading processes.
+
+
+
{{ process.label }}
+
{{ process.description || 'No description' }}
+
+
+
+
+
+
+
+
+
+
+
Select a process from the list to see its details.
+
+
+
+
+ Loading details...
+
+
+
+
+
Process Information
+
+
Name: {{ selectedProcessDetails.processName }}
+
Status: {{ selectedProcessDetails.processStatus }}
+
Description: {{ selectedProcessDetails.processDescription || 'N/A' }}
+
+
{{ contentSummary.nodes }}
Nodes
+
{{ contentSummary.edges }}
Connections
+
{{ contentSummary.variables }}
Variables
+
+
+
+
+
+
Process Nodes
+
+
+
+
+
+
+
{{ node.data?.label || node.label || node.type }}
+
{{ node.id }}
+
+
+
{{ (node.type || 'node').replace(/-/g, ' ') }}
+
+
+
No nodes in this process.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/process-flow/SubprocessNodeConfigurationModal.vue b/components/process-flow/SubprocessNodeConfigurationModal.vue
new file mode 100644
index 0000000..aa53af0
--- /dev/null
+++ b/components/process-flow/SubprocessNodeConfigurationModal.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
Configure Sub-process
+
+ Select a process to execute as a sub-process. The main process will pause and wait for the selected sub-process to complete before continuing.
+
+
+
+
+
+
+
+
+
+
+
+
+ How Sub-processes Work
+
+
+
+ Sub-processes run as independent instances.
+ You can map input variables from the parent process to the sub-process.
+ Output variables from the sub-process can be mapped back to the parent.
+ The parent process will wait until the sub-process reaches an end state.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/json/form/customScript.js b/docs/json/form/customScript.js
index 0c4299b..babe8a0 100644
--- a/docs/json/form/customScript.js
+++ b/docs/json/form/customScript.js
@@ -1,878 +1,41 @@
-this.hideField("form_jeniskp_1");
-this.hideField("form_jeniskp_2");
-this.hideField("form_jeniskp_3");
-
-this.onFieldChange("select_1", (value) => {
- this.hideField("form_jeniskp_1");
- this.hideField("form_jeniskp_2");
- this.hideField("form_jeniskp_3");
- if (value && value.trim()) {
- if (value == "jeniskp_1") this.showField("form_jeniskp_1");
- if (value == "jeniskp_2") this.showField("form_jeniskp_2");
- if (value == "jeniskp_3") this.showField("form_jeniskp_3");
+// Conditional logic for showing 'Nyatakan keperluan lain' field
+onFieldChange("keperluan_mendesak", (value) => {
+ if (Array.isArray(value) && value.includes("lain_lain")) {
+ showField("keperluan_lain_nyatakan");
+ } else {
+ hideField("keperluan_lain_nyatakan");
+ setField("keperluan_lain_nyatakan", "");
}
});
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
+// Show success message on form load
+showInfo(
+ "Sila lengkapkan semua maklumat yang diperlukan untuk penilaian awal."
+);
// Conditional Logic Script
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-
-// Hide "Nyatakan Hubungan Lain-lain" initially
-this.hideField("hubungan_lain_nyatakan");
-
-// Show/hide relationship specification field
-this.onFieldChange("hubungan_keluarga", (value) => {
- if (value && value.includes("lain_lain")) {
- this.showField("hubungan_lain_nyatakan");
- } else {
- this.hideField("hubungan_lain_nyatakan");
- }
-});
-
-// Hide "Sebab Pembayaran Tunai" initially
-this.hideField("sebab_tunai");
-
-// Show/hide cash payment reason field
-this.onFieldChange("cara_pembayaran", (value) => {
- if (value && value.includes("tunai")) {
- this.showField("sebab_tunai");
- } else {
- this.hideField("sebab_tunai");
- }
-});
-
-// Hide education specification field initially
-this.hideField("pendidikan_lain_tanggungan");
-
-// Show/hide education specification field
-this.onFieldChange("pendidikan_tertinggi_tanggungan", (value) => {
- if (value && value.includes("lain_lain")) {
- this.showField("pendidikan_lain_tanggungan");
- } else {
- this.hideField("pendidikan_lain_tanggungan");
- }
-});
-
-// Hide school information initially
-this.hideField("maklumat_sekolah");
-
-// Show/hide school information based on schooling status
-this.onFieldChange("bersekolah_tanggungan", (value) => {
- if (value === "ya") {
- this.showField("maklumat_sekolah");
- } else {
- this.hideField("maklumat_sekolah");
- }
-});
-
-// Handle repeating group conditional logic for each dependent
-this.onFieldChange("tanggungan_maklumat", (value) => {
- if (value && Array.isArray(value)) {
- value.forEach((item, index) => {
- // Handle race specification for each dependent
- if (item.bangsa_tanggungan !== "lain_lain") {
- // Hide the specification field for this item
- const fieldName = `tanggungan_maklumat[${index}].bangsa_lain_tanggungan`;
- // Note: Repeating group field hiding requires specific handling
- }
- });
- }
-});
-
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-
-// Conditional logic for field: hubungan_lain_nyatakan
-onFieldChange("hubungan_keluarga", function () {
+// Conditional logic for field: keperluan_lain_nyatakan
+onFieldChange("keperluan_mendesak", function () {
if (
- !String(getField("hubungan_keluarga") || "")
+ String(getField("keperluan_mendesak") || "")
.toLowerCase()
.includes("lain_lain".toLowerCase())
) {
- hideField("hubungan_lain_nyatakan");
+ showField("keperluan_lain_nyatakan");
} else {
- showField("hubungan_lain_nyatakan");
+ hideField("keperluan_lain_nyatakan");
}
});
-// Initial evaluation for field: hubungan_lain_nyatakan
+// Initial evaluation for field: keperluan_lain_nyatakan
(function () {
if (
- !String(getField("hubungan_keluarga") || "")
+ String(getField("keperluan_mendesak") || "")
.toLowerCase()
.includes("lain_lain".toLowerCase())
) {
- hideField("hubungan_lain_nyatakan");
+ showField("keperluan_lain_nyatakan");
} else {
- showField("hubungan_lain_nyatakan");
- }
-})();
-
-// Conditional logic for field: sebab_tunai
-onFieldChange("cara_pembayaran", function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-});
-
-// Initial evaluation for field: sebab_tunai
-(function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-})();
-
-// Conditional logic for field: pendidikan_lain_tanggungan
-onFieldChange("pendidikan_tertinggi_tanggungan", function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-});
-
-// Initial evaluation for field: pendidikan_lain_tanggungan
-(function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-})();
-
-// Conditional logic for field: maklumat_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("maklumat_sekolah");
- } else {
- showField("maklumat_sekolah");
- }
-});
-
-// Initial evaluation for field: maklumat_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("maklumat_sekolah");
- } else {
- showField("maklumat_sekolah");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-
-// Conditional logic for field: hubungan_lain_nyatakan
-onFieldChange("hubungan_keluarga", function () {
- if (
- !String(getField("hubungan_keluarga") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("hubungan_lain_nyatakan");
- } else {
- showField("hubungan_lain_nyatakan");
- }
-});
-
-// Initial evaluation for field: hubungan_lain_nyatakan
-(function () {
- if (
- !String(getField("hubungan_keluarga") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("hubungan_lain_nyatakan");
- } else {
- showField("hubungan_lain_nyatakan");
- }
-})();
-
-// Conditional logic for field: bangsa_lain_tanggungan
-onFieldChange("bangsa_tanggungan", function () {
- if (getField("bangsa_tanggungan") !== "lain_lain") {
- hideField("bangsa_lain_tanggungan");
- } else {
- showField("bangsa_lain_tanggungan");
- }
-});
-
-// Initial evaluation for field: bangsa_lain_tanggungan
-(function () {
- if (getField("bangsa_tanggungan") !== "lain_lain") {
- hideField("bangsa_lain_tanggungan");
- } else {
- showField("bangsa_lain_tanggungan");
- }
-})();
-
-// Conditional logic for field: sebab_tunai
-onFieldChange("cara_pembayaran", function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-});
-
-// Initial evaluation for field: sebab_tunai
-(function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-})();
-
-// Conditional logic for field: pendidikan_lain_tanggungan
-onFieldChange("pendidikan_tertinggi_tanggungan", function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-});
-
-// Initial evaluation for field: pendidikan_lain_tanggungan
-(function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-})();
-
-// Conditional logic for field: nama_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("nama_sekolah");
- } else {
- showField("nama_sekolah");
- }
-});
-
-// Initial evaluation for field: nama_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("nama_sekolah");
- } else {
- showField("nama_sekolah");
- }
-})();
-
-// Conditional logic for field: alamat_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("alamat_sekolah");
- } else {
- showField("alamat_sekolah");
- }
-});
-
-// Initial evaluation for field: alamat_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("alamat_sekolah");
- } else {
- showField("alamat_sekolah");
- }
-})();
-
-// Conditional logic for field: daerah_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("daerah_sekolah");
- } else {
- showField("daerah_sekolah");
- }
-});
-
-// Initial evaluation for field: daerah_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("daerah_sekolah");
- } else {
- showField("daerah_sekolah");
- }
-})();
-
-// Conditional logic for field: negeri_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("negeri_sekolah");
- } else {
- showField("negeri_sekolah");
- }
-});
-
-// Initial evaluation for field: negeri_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("negeri_sekolah");
- } else {
- showField("negeri_sekolah");
- }
-})();
-
-// Conditional logic for field: poskod_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("poskod_sekolah");
- } else {
- showField("poskod_sekolah");
- }
-});
-
-// Initial evaluation for field: poskod_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("poskod_sekolah");
- } else {
- showField("poskod_sekolah");
- }
-})();
-// Conditional Logic Script
-
-// Conditional logic for field: nyatakan_lain2
-onFieldChange("radio_bangsa", function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-});
-
-// Initial evaluation for field: nyatakan_lain2
-(function () {
- if (getField("radio_bangsa") !== "lain") {
- hideField("nyatakan_lain2");
- } else {
- showField("nyatakan_lain2");
- }
-})();
-
-// Conditional logic for field: text_14
-onFieldChange("radio_pendidikan", function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-});
-
-// Initial evaluation for field: text_14
-(function () {
- if (getField("radio_pendidikan") !== "lain") {
- hideField("text_14");
- } else {
- showField("text_14");
- }
-})();
-
-// Conditional logic for field: hubungan_lain_nyatakan
-onFieldChange("hubungan_keluarga", function () {
- if (
- !String(getField("hubungan_keluarga") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("hubungan_lain_nyatakan");
- } else {
- showField("hubungan_lain_nyatakan");
- }
-});
-
-// Initial evaluation for field: hubungan_lain_nyatakan
-(function () {
- if (
- !String(getField("hubungan_keluarga") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("hubungan_lain_nyatakan");
- } else {
- showField("hubungan_lain_nyatakan");
- }
-})();
-
-// Conditional logic for field: bangsa_lain_tanggungan
-onFieldChange("bangsa_tanggungan", function () {
- if (getField("bangsa_tanggungan") !== "lain_lain") {
- hideField("bangsa_lain_tanggungan");
- } else {
- showField("bangsa_lain_tanggungan");
- }
-});
-
-// Initial evaluation for field: bangsa_lain_tanggungan
-(function () {
- if (getField("bangsa_tanggungan") !== "lain_lain") {
- hideField("bangsa_lain_tanggungan");
- } else {
- showField("bangsa_lain_tanggungan");
- }
-})();
-
-// Conditional logic for field: sebab_tunai
-onFieldChange("cara_pembayaran", function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-});
-
-// Initial evaluation for field: sebab_tunai
-(function () {
- if (
- !String(getField("cara_pembayaran") || "")
- .toLowerCase()
- .includes("tunai".toLowerCase())
- ) {
- hideField("sebab_tunai");
- } else {
- showField("sebab_tunai");
- }
-})();
-
-// Conditional logic for field: pendidikan_lain_tanggungan
-onFieldChange("pendidikan_tertinggi_tanggungan", function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-});
-
-// Initial evaluation for field: pendidikan_lain_tanggungan
-(function () {
- if (
- !String(getField("pendidikan_tertinggi_tanggungan") || "")
- .toLowerCase()
- .includes("lain_lain".toLowerCase())
- ) {
- hideField("pendidikan_lain_tanggungan");
- } else {
- showField("pendidikan_lain_tanggungan");
- }
-})();
-
-// Conditional logic for field: nama_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("nama_sekolah");
- } else {
- showField("nama_sekolah");
- }
-});
-
-// Initial evaluation for field: nama_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("nama_sekolah");
- } else {
- showField("nama_sekolah");
- }
-})();
-
-// Conditional logic for field: alamat_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("alamat_sekolah");
- } else {
- showField("alamat_sekolah");
- }
-});
-
-// Initial evaluation for field: alamat_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("alamat_sekolah");
- } else {
- showField("alamat_sekolah");
- }
-})();
-
-// Conditional logic for field: daerah_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("daerah_sekolah");
- } else {
- showField("daerah_sekolah");
- }
-});
-
-// Initial evaluation for field: daerah_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("daerah_sekolah");
- } else {
- showField("daerah_sekolah");
- }
-})();
-
-// Conditional logic for field: negeri_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("negeri_sekolah");
- } else {
- showField("negeri_sekolah");
- }
-});
-
-// Initial evaluation for field: negeri_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("negeri_sekolah");
- } else {
- showField("negeri_sekolah");
- }
-})();
-
-// Conditional logic for field: poskod_sekolah
-onFieldChange("bersekolah_tanggungan", function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("poskod_sekolah");
- } else {
- showField("poskod_sekolah");
- }
-});
-
-// Initial evaluation for field: poskod_sekolah
-(function () {
- if (getField("bersekolah_tanggungan") !== "ya") {
- hideField("poskod_sekolah");
- } else {
- showField("poskod_sekolah");
+ hideField("keperluan_lain_nyatakan");
}
})();
diff --git a/docs/json/form/formComponents.json b/docs/json/form/formComponents.json
index 2516ad2..8c4ce10 100644
--- a/docs/json/form/formComponents.json
+++ b/docs/json/form/formComponents.json
@@ -2,9 +2,9 @@
{
"type": "heading",
"props": {
- "name": "heading_2",
- "level": 2,
- "value": "SEKSYEN A",
+ "name": "heading_main",
+ "level": 1,
+ "value": "Penilaian Awal",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
@@ -16,829 +16,10 @@
}
},
{
- "type": "heading",
+ "type": "paragraph",
"props": {
- "name": "heading_1",
- "level": 3,
- "value": "1. MAKLUMAT PENDAFTARAN ASNAF (KETUA KELUARGA)",
- "width": "100%",
- "gridColumn": "span 12",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "Untuk Mualaf, nama mengikut kad pengenalan",
- "name": "text_3",
- "type": "text",
- "label": "Nama",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "help": "",
- "name": "select_1",
- "type": "select",
- "label": "Jenis Kad Pengenalan",
- "width": "100%",
- "options": [
- { "label": "Sila Pilih Jenis Kad Pengenalan", "value": "jeniskp" },
- { "label": "MyKad/MyKid", "value": "jeniskp_1" },
- { "label": "No. K/P/Polis/Tentera/No. Pasport", "value": "jeniskp_2" },
- { "label": "No. Sijil Beranak", "value": "jeniskp_3" }
- ],
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "",
- "name": "form_jeniskp_1",
- "type": "text",
- "label": "MyKad/MyKid",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "",
- "name": "form_jeniskp_2",
- "type": "text",
- "label": "No. K/P/Polis/Tentera/No. Pasport",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "",
- "name": "form_jeniskp_3",
- "type": "text",
- "label": "No. Sijil Beranak",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "Isi Jika Berkenaan",
- "name": "text_warganegara",
- "type": "text",
- "label": "Warganegara",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "radio",
- "props": {
- "help": "",
- "name": "radio_jantina",
- "type": "radio",
- "label": "Jantina",
- "width": "50%",
- "options": [
- { "label": "Lelaki", "value": "lelaki" },
- { "label": "Perempuan", "value": "perempuan" }
- ],
- "gridColumn": "span 6",
- "validation": "required",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "help": "",
- "name": "radio_bangsa",
- "type": "select",
- "label": "Bangsa",
- "width": "50%",
- "options": [
- { "label": "Melayu", "value": "melayu" },
- { "label": "Cina", "value": "cina" },
- { "label": "India", "value": "india" },
- { "label": "Lain-lain", "value": "lain" }
- ],
- "gridColumn": "span 6",
- "validation": "required",
- "placeholder": "Select an option"
- }
- },
- {
- "type": "text",
- "props": {
- "help": "",
- "name": "nyatakan_lain2",
- "type": "text",
- "label": "Nyatakan (Bangsa Lain-Lain)",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Enter text...",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- { "field": "radio_bangsa", "value": "lain", "operator": "not_equals" }
- ]
- }
- }
- },
- {
- "type": "radio",
- "props": {
- "help": "",
- "name": "radio_9_copy",
- "type": "radio",
- "label": "Bersekolah",
- "width": "50%",
- "options": [
- { "label": "Ya", "value": "ya" },
- { "label": "Tidak", "value": "tidak" }
- ],
- "gridColumn": "span 6",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "help": "",
- "name": "radio_pendidikan",
- "type": "select",
- "label": "Pendidikan Tertinggi",
- "width": "50%",
- "options": [
- { "label": "Peringkat Rendah", "value": "rendah" },
- { "label": "SRP/PMR", "value": "srp" },
- { "label": "SPM", "value": "spm" },
- { "label": "Sijil", "value": "sijil" },
- { "label": "Diploma", "value": "diploma" },
- { "label": "STPM", "value": "stpm" },
- { "label": "Ijazah", "value": "ijazah" },
- { "label": "Lain-Lain", "value": "lain" }
- ],
- "gridColumn": "span 6",
- "validation": "required",
- "placeholder": "Select an option"
- }
- },
- {
- "type": "text",
- "props": {
- "help": "",
- "name": "text_14",
- "type": "text",
- "label": "Nyatakan (Pendidikan Tertinggi Lain-Lain)",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Enter text...",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "radio_pendidikan",
- "value": "lain",
- "operator": "not_equals"
- }
- ]
- }
- }
- },
- {
- "type": "date",
- "props": {
- "help": "Isi Jika Berkenaan",
- "name": "date_masukislam",
- "type": "date",
- "label": "Tarikh Masuk Islam",
- "width": "50%",
- "gridColumn": "span 6",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "date",
- "props": {
- "help": "Isi Jika Berkenaan",
- "name": "date_masukislam_copy",
- "type": "date",
- "label": "Tarikh Mula Kelas fardu Ain Muallaf (KFAM)",
- "width": "50%",
- "gridColumn": "span 6",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "help": "",
- "name": "select_statusperkahwinan",
- "type": "select",
- "label": "Status Perkahwinan",
- "width": "100%",
- "options": [
- { "label": "Berkahwin", "value": "berkahwin" },
- { "label": "Ibu Tinggal/Bapa Tinggal", "value": "ibubapatunggal" },
- { "label": "Bujang", "value": "bujang" },
- { "label": "Duda", "value": "duda" },
- { "label": "Janda", "value": "janda" },
- { "label": "Balu", "value": "balu" }
- ],
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "Select an option",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "heading",
- "props": {
- "name": "heading_seksyen_b",
- "level": 2,
- "value": "SEKSYEN B",
- "width": "100%",
- "gridColumn": "span 12",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "heading",
- "props": {
- "name": "heading_tanggungan",
- "level": 3,
- "value": "1. MAKLUMAT PERIBADI TANGGUNGAN (Ruangan WAJIB diisi)",
- "width": "100%",
- "gridColumn": "span 12",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "checkbox",
- "props": {
- "help": "Pilih semua yang berkenaan",
- "name": "hubungan_keluarga",
- "type": "checkbox",
- "label": "Hubungan dengan Pemohon/Asnaf",
- "width": "100%",
- "options": [
- { "label": "Pasangan Pemohon", "value": "pasangan" },
- { "label": "Isteri Kedua", "value": "isteri_kedua" },
- { "label": "Isteri Ketiga", "value": "isteri_ketiga" },
- { "label": "Isteri Keempat", "value": "isteri_keempat" },
- { "label": "Ipar", "value": "ipar" },
- { "label": "Abang", "value": "abang" },
- { "label": "Bapa", "value": "bapa" },
- { "label": "Ibu", "value": "ibu" },
- { "label": "Kakak", "value": "kakak" },
- { "label": "Adik", "value": "adik" },
- { "label": "Anak", "value": "anak" },
- { "label": "Cucu", "value": "cucu" },
- { "label": "Bapa Mertua", "value": "bapa_mertua" },
- { "label": "Ibu Mertua", "value": "ibu_mertua" },
- { "label": "Lain-lain (Sila Nyatakan)", "value": "lain_lain" }
- ],
- "gridColumn": "span 12",
- "validation": "required"
- }
- },
- {
- "type": "text",
- "props": {
- "name": "hubungan_lain_nyatakan",
- "type": "text",
- "label": "Nyatakan Hubungan Lain-lain",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Sila nyatakan hubungan lain-lain",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "hubungan_keluarga",
- "value": "lain_lain",
- "operator": "not_contains"
- }
- ]
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "nama_tanggungan",
- "type": "text",
- "label": "Nama (Untuk Mualaf, nama mengikut kad pengenalan)",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "Masukkan nama lengkap",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "name": "jenis_kad_tanggungan",
- "type": "select",
- "label": "Jenis Pengenalan",
- "width": "100%",
- "options": [
- { "label": "Sila Pilih Jenis Pengenalan", "value": "" },
- { "label": "MyKad/MyKid", "value": "mykad" },
- { "label": "No. K/P/Polis/Tentera/No. Pasport", "value": "kp_polis" },
- { "label": "No. Sijil Beranak", "value": "sijil_beranak" }
- ],
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "Sila pilih jenis pengenalan",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "no_pengenalan_tanggungan",
- "type": "text",
- "label": "No. Pengenalan",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "Masukkan nombor pengenalan",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "radio",
- "props": {
- "name": "jantina_tanggungan",
- "type": "radio",
- "label": "Jantina",
- "width": "100%",
- "options": [
- { "label": "Lelaki", "value": "lelaki" },
- { "label": "Perempuan", "value": "perempuan" }
- ],
- "gridColumn": "span 12",
- "validation": "required",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "date",
- "props": {
- "name": "tarikh_lahir_tanggungan",
- "type": "date",
- "label": "Tarikh Lahir",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "required",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "tempat_lahir_tanggungan",
- "type": "text",
- "label": "Tempat Lahir",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Masukkan tempat lahir",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "select",
- "props": {
- "name": "bangsa_tanggungan",
- "type": "select",
- "label": "Bangsa",
- "width": "100%",
- "options": [
- { "label": "Sila Pilih Bangsa", "value": "" },
- { "label": "Melayu", "value": "melayu" },
- { "label": "Cina", "value": "cina" },
- { "label": "India", "value": "india" },
- { "label": "Lain-lain (Sila Nyatakan)", "value": "lain_lain" }
- ],
- "gridColumn": "span 12",
- "validation": "required",
- "placeholder": "Sila pilih bangsa",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "bangsa_lain_tanggungan",
- "type": "text",
- "label": "Nyatakan Bangsa Lain-lain",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Sila nyatakan bangsa lain-lain",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "bangsa_tanggungan",
- "value": "lain_lain",
- "operator": "not_equals"
- }
- ]
- }
- }
- },
- {
- "type": "select",
- "props": {
- "name": "status_kahwin_tanggungan",
- "type": "select",
- "label": "Status Perkahwinan",
- "width": "100%",
- "options": [
- { "label": "Sila Pilih Status Perkahwinan", "value": "" },
- { "label": "Berkahwin", "value": "berkahwin" },
- { "label": "Ibu Tinggal/Bapa Tinggal", "value": "ibu_bapa_tinggal" },
- { "label": "Bujang", "value": "bujang" },
- { "label": "Duda", "value": "duda" },
- { "label": "Janda", "value": "janda" },
- { "label": "Balu", "value": "balu" }
- ],
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Sila pilih status perkahwinan",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "date",
- "props": {
- "help": "Jika Berkenaan",
- "name": "tarikh_masuk_islam_tanggungan",
- "type": "date",
- "label": "Tarikh Masuk Islam",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "date",
- "props": {
- "help": "Jika Berkenaan",
- "name": "tarikh_mula_kfam_tanggungan",
- "type": "date",
- "label": "Tarikh Mula KFAM",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "help": "Jika Berkenaan",
- "name": "warganegara_tanggungan",
- "type": "text",
- "label": "Warganegara",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Masukkan warganegara",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "tempat_menetap_tanggungan",
- "type": "text",
- "label": "Tempoh Menetap Di Selangor",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Contoh: 5 Tahun",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "no_telefon_tanggungan",
- "type": "text",
- "label": "No. Telefon/Telefon Bimbit",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Contoh: 03-12345678",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "heading",
- "props": {
- "name": "heading_maklumat_perbankan",
- "level": 3,
- "value": "MAKLUMAT PERBANKAN (Jika Berkenaan)",
- "width": "100%",
- "gridColumn": "span 12",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "nama_pemegang_akaun",
- "type": "text",
- "label": "Nama Pemegang Akaun",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Masukkan nama pemegang akaun",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "nama_bank",
- "type": "text",
- "label": "Bank",
- "width": "50%",
- "gridColumn": "span 6",
- "validation": "",
- "placeholder": "Masukkan nama bank",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "no_akaun_bank",
- "type": "text",
- "label": "No. Akaun Bank",
- "width": "50%",
- "gridColumn": "span 6",
- "validation": "",
- "placeholder": "Masukkan nombor akaun",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "checkbox",
- "props": {
- "name": "cara_pembayaran",
- "type": "checkbox",
- "label": "Cara Pembayaran",
- "width": "100%",
- "options": [
- { "label": "Akaun", "value": "akaun" },
- { "label": "Cek", "value": "cek" },
- { "label": "Tunai, Nyatakan Sebab", "value": "tunai" },
- { "label": "Uzur/Sakit", "value": "uzur_sakit" },
- { "label": "Muflis", "value": "muflis" },
- { "label": "Disenarai Hitam", "value": "disenarai_hitam" }
- ],
- "gridColumn": "span 12",
- "validation": "",
- "conditionalLogic": {
- "action": "show",
- "enabled": false,
- "operator": "and",
- "conditions": []
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "sebab_tunai",
- "type": "text",
- "label": "Sebab Pembayaran Tunai",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Sila nyatakan sebab",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "cara_pembayaran",
- "value": "tunai",
- "operator": "not_contains"
- }
- ]
- }
- }
- },
- {
- "type": "heading",
- "props": {
- "name": "heading_pendidikan",
- "level": 3,
- "value": "2. PENDIDIKAN",
+ "name": "paragraph_intro",
+ "value": "Sila lengkapkan borang penilaian awal ini untuk membantu kami memahami keperluan anda dengan lebih baik.",
"width": "100%",
"gridColumn": "span 12",
"conditionalLogic": {
@@ -852,9 +33,10 @@
{
"type": "radio",
"props": {
- "name": "bersekolah_tanggungan",
+ "help": "Sila nyatakan sama ada anda mempunyai komitmen kewangan yang tinggi",
+ "name": "komitmen_pembiayaan",
"type": "radio",
- "label": "Bersekolah",
+ "label": "1. Adakah tuan/puan mempunyai komitmen dan pembiayaan melibatkan kos yang tinggi?",
"width": "100%",
"options": [
{ "label": "Ya", "value": "ya" },
@@ -873,32 +55,26 @@
{
"type": "checkbox",
"props": {
- "name": "pendidikan_tertinggi_tanggungan",
+ "help": "Sila pilih keperluan yang mendesak. Boleh pilih lebih daripada satu.",
+ "name": "keperluan_mendesak",
"type": "checkbox",
- "label": "Pendidikan Tertinggi",
+ "label": "2. Apakah keperluan tuan/puan mendesak sekarang ini?",
"width": "100%",
"options": [
- { "label": "Peringkat Rendah", "value": "peringkat_rendah" },
- { "label": "SRP/PMR", "value": "srp_pmr" },
- { "label": "SPM", "value": "spm" },
- { "label": "STPM", "value": "stpm" },
- { "label": "Pra Sekolah", "value": "pra_sekolah" },
- { "label": "Sekolah Rendah Kebangsaan", "value": "srk" },
- { "label": "Sekolah Menengah Kebangsaan", "value": "smk" },
- { "label": "Sekolah Menengah Agama", "value": "sma" },
- { "label": "Sijil", "value": "sijil" },
- { "label": "Diploma", "value": "diploma" },
- { "label": "Ijazah", "value": "ijazah" },
- { "label": "Lain-Lain (Sila Nyatakan)", "value": "lain_lain" },
+ { "label": "Perubatan Kritikal", "value": "perubatan_kritikal" },
+ { "label": "Bencana", "value": "bencana" },
+ { "label": "Kematian", "value": "kematian" },
{
- "label": "Sekolah Rendah Kebangsaan dan Agama",
- "value": "srk_agama"
+ "label": "Konflik Keluarga (tiada tempat bergantung)",
+ "value": "konflik_keluarga"
},
- { "label": "IPTA/IPTS", "value": "ipta_ipts" },
- { "label": "Maahad Tahfiz", "value": "maahad_tahfiz" }
+ { "label": "Tiada Tempat Tinggal", "value": "tiada_tempat_tinggal" },
+ { "label": "Tunggakan Bil Utiliti", "value": "tunggakan_utiliti" },
+ { "label": "Selain dari di atas", "value": "lain_lain" },
+ { "label": "Tidak mendesak", "value": "tidak_mendesak" }
],
"gridColumn": "span 12",
- "validation": "",
+ "validation": "required",
"conditionalLogic": {
"action": "show",
"enabled": false,
@@ -907,164 +83,98 @@
}
}
},
- {
- "type": "text",
- "props": {
- "name": "pendidikan_lain_tanggungan",
- "type": "text",
- "label": "Nyatakan Pendidikan Lain-lain",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Sila nyatakan pendidikan lain-lain",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "pendidikan_tertinggi_tanggungan",
- "value": "lain_lain",
- "operator": "not_contains"
- }
- ]
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "nama_sekolah",
- "type": "text",
- "label": "Nama Sekolah/Institusi",
- "width": "100%",
- "gridColumn": "span 12",
- "validation": "",
- "placeholder": "Masukkan nama sekolah/institusi",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "bersekolah_tanggungan",
- "value": "ya",
- "operator": "not_equals"
- }
- ]
- }
- }
- },
{
"type": "textarea",
"props": {
- "name": "alamat_sekolah",
+ "help": "Sila nyatakan keperluan lain yang tidak disenaraikan di atas",
+ "name": "keperluan_lain_nyatakan",
+ "rows": 3,
"type": "textarea",
- "label": "Alamat Sekolah/Institusi",
+ "label": "Nyatakan keperluan lain:",
"width": "100%",
"gridColumn": "span 12",
"validation": "",
- "placeholder": "Masukkan alamat lengkap sekolah/institusi",
+ "placeholder": "Sila nyatakan keperluan anda...",
"conditionalLogic": {
- "action": "hide",
+ "action": "show",
"enabled": true,
"operator": "and",
"conditions": [
{
- "field": "bersekolah_tanggungan",
- "value": "ya",
- "operator": "not_equals"
+ "field": "keperluan_mendesak",
+ "value": "lain_lain",
+ "operator": "contains"
}
]
}
}
},
{
- "type": "text",
+ "type": "file",
"props": {
- "name": "daerah_sekolah",
- "type": "text",
- "label": "Daerah",
- "width": "50%",
- "gridColumn": "span 6",
+ "help": "Sila muat naik dokumen yang berkaitan dengan permohonan anda. Format yang diterima: PDF, DOC, DOCX, JPG, PNG. Saiz maksimum: 10MB",
+ "name": "dokumen_berkaitan",
+ "type": "file",
+ "label": "3. Upload dokumen yang berkaitan",
+ "width": "100%",
+ "accept": ".pdf,.doc,.docx,.jpg,.jpeg,.png",
+ "multiple": true,
+ "gridColumn": "span 12",
"validation": "",
- "placeholder": "Masukkan daerah",
"conditionalLogic": {
- "action": "hide",
- "enabled": true,
+ "action": "show",
+ "enabled": false,
"operator": "and",
- "conditions": [
- {
- "field": "bersekolah_tanggungan",
- "value": "ya",
- "operator": "not_equals"
- }
- ]
+ "conditions": []
}
}
},
{
- "type": "text",
+ "type": "textarea",
"props": {
- "name": "negeri_sekolah",
- "type": "text",
- "label": "Negeri",
- "width": "50%",
- "gridColumn": "span 6",
- "validation": "",
- "placeholder": "Masukkan negeri",
- "conditionalLogic": {
- "action": "hide",
- "enabled": true,
- "operator": "and",
- "conditions": [
- {
- "field": "bersekolah_tanggungan",
- "value": "ya",
- "operator": "not_equals"
- }
- ]
- }
- }
- },
- {
- "type": "text",
- "props": {
- "name": "poskod_sekolah",
- "type": "text",
- "label": "Poskod",
+ "help": "Sila berikan catatan tambahan yang boleh membantu kami memahami permohonan anda",
+ "name": "catatan_tambahan",
+ "rows": 5,
+ "type": "textarea",
+ "label": "4. Catatan tambahan berkaitan permohonan",
"width": "100%",
"gridColumn": "span 12",
"validation": "",
- "placeholder": "Masukkan poskod",
+ "placeholder": "Sila berikan catatan tambahan...",
"conditionalLogic": {
- "action": "hide",
- "enabled": true,
+ "action": "show",
+ "enabled": false,
"operator": "and",
- "conditions": [
- {
- "field": "bersekolah_tanggungan",
- "value": "ya",
- "operator": "not_equals"
- }
- ]
+ "conditions": []
}
}
},
{
- "type": "radio",
+ "type": "divider",
"props": {
- "name": "tinggal_bersama_keluarga",
- "type": "radio",
- "label": "Tinggal Bersama Keluarga",
+ "name": "divider_submit",
"width": "100%",
- "options": [
- { "label": "Ya", "value": "ya" },
- { "label": "Tidak", "value": "tidak" },
- { "label": "Asrama", "value": "asrama" }
- ],
"gridColumn": "span 12",
- "validation": "",
+ "conditionalLogic": {
+ "action": "show",
+ "enabled": false,
+ "operator": "and",
+ "conditions": []
+ }
+ }
+ },
+ {
+ "type": "button",
+ "props": {
+ "name": "submit_button",
+ "size": "lg",
+ "label": "Hantar Penilaian Awal",
+ "width": "100%",
+ "onClick": "",
+ "variant": "primary",
+ "disabled": false,
+ "buttonType": "submit",
+ "gridColumn": "span 12",
"conditionalLogic": {
"action": "show",
"enabled": false,
diff --git a/docs/json/process-builder/processDefinition.json b/docs/json/process-builder/processDefinition.json
index 418c560..b77aded 100644
--- a/docs/json/process-builder/processDefinition.json
+++ b/docs/json/process-builder/processDefinition.json
@@ -1,18 +1,51 @@
{
"edges": [
{
- "id": "start-1751870920411-form-1751870928350-1751954902366",
+ "id": "start-1751870920411-form-1752471000000-1752110219601",
"data": {},
"type": "smoothstep",
"label": "",
"source": "start-1751870920411",
- "target": "form-1751870928350",
+ "target": "form-1752471000000",
"animated": true,
"sourceHandle": "start-1751870920411-right",
+ "targetHandle": "form-1752471000000-left"
+ },
+ {
+ "id": "form-1752471000000-api-1752471000010-1752110221444",
+ "data": {},
+ "type": "smoothstep",
+ "label": "",
+ "source": "form-1752471000000",
+ "target": "api-1752471000010",
+ "animated": true,
+ "sourceHandle": "form-1752471000000-bottom",
+ "targetHandle": "api-1752471000010-top"
+ },
+ {
+ "id": "api-1752471000010-script-1752471000020-1752110222889",
+ "data": {},
+ "type": "smoothstep",
+ "label": "",
+ "source": "api-1752471000010",
+ "target": "script-1752471000020",
+ "animated": true,
+ "sourceHandle": "api-1752471000010-right",
+ "targetHandle": "script-1752471000020-left"
+ },
+ {
+ "id": "script-1752471000020-form-1751870928350-1752110513125",
+ "data": {},
+ "type": "smoothstep",
+ "label": "",
+ "source": "script-1752471000020",
+ "target": "form-1751870928350",
+ "animated": true,
+ "sourceHandle": "script-1752471000020-right",
"targetHandle": "form-1751870928350-left"
},
{
- "id": "form-1751870928350-api-1751871528249-1751954924255",
+ "id": "form-1751870928350-api-1751871528249-1752110547688",
"data": {},
"type": "smoothstep",
"label": "",
@@ -23,7 +56,7 @@
"targetHandle": "api-1751871528249-top"
},
{
- "id": "api-1751871528249-script-1751871635000-1751954926618",
+ "id": "api-1751871528249-script-1751871635000-1752110661170",
"data": {},
"type": "smoothstep",
"label": "",
@@ -34,103 +67,59 @@
"targetHandle": "script-1751871635000-left"
},
{
- "id": "form-1751871700000-script-1751871635000-1751954928000",
+ "id": "script-1751871635000-api-1752114771983-1752114797295",
"data": {},
"type": "smoothstep",
"label": "",
- "source": "form-1751871700000",
- "target": "script-1751871635000",
+ "source": "script-1751871635000",
+ "target": "api-1752114771983",
"animated": true,
- "sourceHandle": "form-1751871700000-bottom",
- "targetHandle": "script-1751871635000-top"
+ "sourceHandle": "script-1751871635000-right",
+ "targetHandle": "api-1752114771983-left"
},
{
- "id": "api-1751871750000-form-1751871700000-1751954936240",
+ "id": "api-1752114771983-html-1752109761532-1752114821740",
"data": {},
"type": "smoothstep",
"label": "",
- "source": "api-1751871750000",
- "target": "form-1751871700000",
+ "source": "api-1752114771983",
+ "target": "html-1752109761532",
"animated": true,
- "sourceHandle": "api-1751871750000-bottom",
- "targetHandle": "form-1751871700000-top"
+ "sourceHandle": "api-1752114771983-right",
+ "targetHandle": "html-1752109761532-left"
},
{
- "id": "api-1751871750000-script-1751871770000-1751954938889",
+ "id": "start-1751870920411-form-1753000000000-1752115210580",
"data": {},
"type": "smoothstep",
"label": "",
- "source": "api-1751871750000",
- "target": "script-1751871770000",
+ "source": "start-1751870920411",
+ "target": "form-1753000000000",
"animated": true,
- "sourceHandle": "api-1751871750000-right",
- "targetHandle": "script-1751871770000-left"
+ "sourceHandle": "start-1751870920411-right",
+ "targetHandle": "form-1753000000000-left"
},
{
- "id": "script-1751871770000-gateway-1751871800000-1751954943222",
+ "id": "form-1753000000000-api-1753000000001-1752115217959",
"data": {},
"type": "smoothstep",
"label": "",
- "source": "script-1751871770000",
- "target": "gateway-1751871800000",
+ "source": "form-1753000000000",
+ "target": "api-1753000000001",
"animated": true,
- "sourceHandle": "script-1751871770000-bottom",
- "targetHandle": "gateway-1751871800000-left"
+ "sourceHandle": "form-1753000000000-bottom",
+ "targetHandle": "api-1753000000001-top"
},
{
- "id": "gateway-1751871800000-business-rule-1751871900000-1751954958263",
- "data": {},
- "type": "smoothstep",
- "label": "Ya",
- "source": "gateway-1751871800000",
- "target": "business-rule-1751871900000",
- "animated": true,
- "sourceHandle": "gateway-1751871800000-right",
- "targetHandle": "business-rule-1751871900000-left"
- },
- {
- "id": "gateway-1751871800000-notification-1751872000000-1751954960514",
- "data": {},
- "type": "smoothstep",
- "label": "Tidak",
- "source": "gateway-1751871800000",
- "target": "notification-1751872000000",
- "animated": true,
- "sourceHandle": "gateway-1751871800000-right",
- "targetHandle": "notification-1751872000000-left"
- },
- {
- "id": "business-rule-1751871900000-notification-1751871950000-1751954963756",
+ "id": "api-1753000000001-script-1753000000002-1752115222952",
"data": {},
"type": "smoothstep",
"label": "",
- "source": "business-rule-1751871900000",
- "target": "notification-1751871950000",
+ "source": "api-1753000000001",
+ "target": "script-1753000000002",
"animated": true,
- "sourceHandle": "business-rule-1751871900000-right",
- "targetHandle": "notification-1751871950000-left"
- },
- {
- "id": "notification-1751871950000-end-1751872100000-1751954966017",
- "data": {},
- "type": "smoothstep",
- "label": "",
- "source": "notification-1751871950000",
- "target": "end-1751872100000",
- "animated": true,
- "sourceHandle": "notification-1751871950000-bottom",
- "targetHandle": "end-1751872100000-top"
- },
- {
- "id": "notification-1751872000000-end-1751872100000-1751954967691",
- "data": {},
- "type": "smoothstep",
- "label": "",
- "source": "notification-1751872000000",
- "target": "end-1751872100000",
- "animated": true,
- "sourceHandle": "notification-1751872000000-right",
- "targetHandle": "end-1751872100000-left"
+ "sourceHandle": "api-1753000000001-right",
+ "targetHandle": "script-1753000000002-left"
}
],
"nodes": [
@@ -139,7 +128,165 @@
"data": { "label": "Start", "description": "Process start point" },
"type": "start",
"label": "Start",
- "position": { "x": 210, "y": 180 }
+ "position": { "x": 120, "y": -495 }
+ },
+ {
+ "id": "form-1752471000000",
+ "data": {
+ "label": "Penilaian Awal",
+ "formId": 3,
+ "formName": "Penilaian Awal",
+ "formUuid": "8e07fc8f-a160-478a-85fd-fa3364401544",
+ "description": "Form: Penilaian Awal untuk permohonan bantuan",
+ "assignedRoles": [],
+ "assignedUsers": [],
+ "inputMappings": [],
+ "assignmentType": "public",
+ "outputMappings": [
+ {
+ "formField": "komitmen_pembiayaan",
+ "processVariable": "komitmenKosTinggi"
+ },
+ {
+ "formField": "keperluan_mendesak",
+ "processVariable": "keperluanMendesak"
+ },
+ {
+ "formField": "keperluan_lain_nyatakan",
+ "processVariable": "keperluanLainNyatakan"
+ },
+ {
+ "formField": "dokumen_berkaitan",
+ "processVariable": "dokumenBerkaitan"
+ },
+ {
+ "formField": "catatan_tambahan",
+ "processVariable": "catatanTambahan"
+ }
+ ],
+ "fieldConditions": [],
+ "assignmentVariable": "",
+ "assignmentVariableType": "user_id"
+ },
+ "type": "form",
+ "label": "Penilaian Awal",
+ "position": { "x": 450, "y": -525 }
+ },
+ {
+ "id": "api-1752471000010",
+ "data": {
+ "label": "Submit Penilaian Awal API",
+ "apiUrl": "https://jsonplaceholder.typicode.com/posts",
+ "headers": "{ \"Content-Type\": \"application/json\" }",
+ "apiMethod": "POST",
+ "description": "Submit penilaian awal data to external system",
+ "requestBody": "{\n \"komitmenKosTinggi\": \"{komitmenKosTinggi}\",\n \"keperluanMendesak\": \"{keperluanMendesak}\",\n \"keperluanLainNyatakan\": \"{keperluanLainNyatakan}\",\n \"dokumenBerkaitan\": \"{dokumenBerkaitan}\",\n \"catatanTambahan\": \"{catatanTambahan}\"\n}",
+ "errorVariable": "penilaianAwalApiError",
+ "outputVariable": "penilaianAwalApiResponse",
+ "continueOnError": false
+ },
+ "type": "api",
+ "label": "Submit Penilaian Awal API",
+ "position": { "x": 450, "y": -345 }
+ },
+ {
+ "id": "script-1752471000020",
+ "data": {
+ "label": "Process Penilaian Awal Response",
+ "scriptCode": "// Extract important data from Penilaian Awal API response\nconst apiData = processVariables.penilaianAwalApiResponse;\n\nif (apiData && apiData.data) {\n // Generate a reference number for the assessment\n processVariables.penilaianAwalId = apiData.data.id || 'PA-' + Date.now();\n \n // Process the high cost commitment answer\n processVariables.hasHighCostCommitment = processVariables.komitmenKosTinggi === 'ya';\n \n // Process urgent needs\n if (Array.isArray(processVariables.keperluanMendesak)) {\n // Set flags for specific urgent needs\n processVariables.hasUrgentMedicalNeed = processVariables.keperluanMendesak.includes('perubatan_kritikal');\n processVariables.hasDisasterNeed = processVariables.keperluanMendesak.includes('bencana');\n processVariables.hasDeathRelatedNeed = processVariables.keperluanMendesak.includes('kematian');\n processVariables.hasFamilyConflict = processVariables.keperluanMendesak.includes('konflik_keluarga');\n processVariables.hasHomelessness = processVariables.keperluanMendesak.includes('tiada_tempat_tinggal');\n processVariables.hasUtilityArrears = processVariables.keperluanMendesak.includes('tunggakan_utiliti');\n processVariables.hasOtherNeeds = processVariables.keperluanMendesak.includes('lain_lain');\n processVariables.hasNoUrgentNeeds = processVariables.keperluanMendesak.includes('tidak_mendesak');\n \n // Calculate urgency score based on selected needs\n let urgencyScore = 0;\n if (processVariables.hasUrgentMedicalNeed) urgencyScore += 5;\n if (processVariables.hasDisasterNeed) urgencyScore += 5;\n if (processVariables.hasDeathRelatedNeed) urgencyScore += 4;\n if (processVariables.hasFamilyConflict) urgencyScore += 3;\n if (processVariables.hasHomelessness) urgencyScore += 5;\n if (processVariables.hasUtilityArrears) urgencyScore += 2;\n if (processVariables.hasOtherNeeds) urgencyScore += 1;\n if (processVariables.hasNoUrgentNeeds) urgencyScore = 0;\n \n processVariables.urgencyScore = urgencyScore;\n processVariables.urgencyLevel = urgencyScore >= 5 ? 'high' : (urgencyScore >= 3 ? 'medium' : 'low');\n }\n \n // Check if documents were uploaded\n processVariables.hasUploadedDocuments = processVariables.dokumenBerkaitan && \n Array.isArray(processVariables.dokumenBerkaitan) && \n processVariables.dokumenBerkaitan.length > 0;\n \n // Set status for next step\n processVariables.penilaianAwalStatus = 'completed';\n processVariables.readyForPersonalInfo = true;\n \n console.log('Penilaian Awal processed successfully:', {\n penilaianAwalId: processVariables.penilaianAwalId,\n urgencyLevel: processVariables.urgencyLevel,\n urgencyScore: processVariables.urgencyScore,\n hasHighCostCommitment: processVariables.hasHighCostCommitment,\n hasUploadedDocuments: processVariables.hasUploadedDocuments\n });\n} else {\n // Handle API error case\n processVariables.penilaianAwalStatus = 'failed';\n processVariables.readyForPersonalInfo = false;\n processVariables.penilaianAwalError = 'Failed to submit penilaian awal';\n}",
+ "description": "Process the penilaian awal form data and API response",
+ "errorVariable": "penilaianAwalScriptError",
+ "inputVariables": [
+ "penilaianAwalApiResponse",
+ "komitmenKosTinggi",
+ "keperluanMendesak",
+ "keperluanLainNyatakan",
+ "dokumenBerkaitan",
+ "catatanTambahan"
+ ],
+ "scriptLanguage": "javascript",
+ "continueOnError": false,
+ "outputVariables": [
+ {
+ "name": "penilaianAwalId",
+ "type": "string",
+ "description": "Generated ID for the initial assessment"
+ },
+ {
+ "name": "hasHighCostCommitment",
+ "type": "boolean",
+ "description": "Whether applicant has high cost commitments"
+ },
+ {
+ "name": "urgencyScore",
+ "type": "number",
+ "description": "Calculated urgency score based on needs"
+ },
+ {
+ "name": "urgencyLevel",
+ "type": "string",
+ "description": "Urgency level (high/medium/low)"
+ },
+ {
+ "name": "hasUrgentMedicalNeed",
+ "type": "boolean",
+ "description": "Whether applicant has urgent medical needs"
+ },
+ {
+ "name": "hasDisasterNeed",
+ "type": "boolean",
+ "description": "Whether applicant has disaster-related needs"
+ },
+ {
+ "name": "hasDeathRelatedNeed",
+ "type": "boolean",
+ "description": "Whether applicant has death-related needs"
+ },
+ {
+ "name": "hasFamilyConflict",
+ "type": "boolean",
+ "description": "Whether applicant has family conflict"
+ },
+ {
+ "name": "hasHomelessness",
+ "type": "boolean",
+ "description": "Whether applicant is homeless"
+ },
+ {
+ "name": "hasUtilityArrears",
+ "type": "boolean",
+ "description": "Whether applicant has utility arrears"
+ },
+ {
+ "name": "hasOtherNeeds",
+ "type": "boolean",
+ "description": "Whether applicant has other needs"
+ },
+ {
+ "name": "hasNoUrgentNeeds",
+ "type": "boolean",
+ "description": "Whether applicant has no urgent needs"
+ },
+ {
+ "name": "hasUploadedDocuments",
+ "type": "boolean",
+ "description": "Whether documents were uploaded"
+ },
+ {
+ "name": "penilaianAwalStatus",
+ "type": "string",
+ "description": "Status of initial assessment submission"
+ },
+ {
+ "name": "readyForPersonalInfo",
+ "type": "boolean",
+ "description": "Whether ready for personal info form"
+ }
+ ]
+ },
+ "type": "script",
+ "label": "Process Penilaian Awal Response",
+ "position": { "x": 780, "y": -345 }
},
{
"id": "form-1751870928350",
@@ -287,7 +434,7 @@
},
"type": "form",
"label": "Borang Maklumat Peribadi",
- "position": { "x": 375, "y": 120 }
+ "position": { "x": 1275, "y": -525 }
},
{
"id": "api-1751871528249",
@@ -304,7 +451,7 @@
},
"type": "api",
"label": "Submit Profile API",
- "position": { "x": 375, "y": 360 }
+ "position": { "x": 1275, "y": -345 }
},
{
"id": "script-1751871635000",
@@ -383,7 +530,7 @@
},
"type": "script",
"label": "Process API Response",
- "position": { "x": 720, "y": 360 }
+ "position": { "x": 1590, "y": -345 }
},
{
"id": "form-1751871700000",
@@ -425,7 +572,7 @@
},
"type": "form",
"label": "Borang Semak Dokumen",
- "position": { "x": 720, "y": 120 }
+ "position": { "x": 885, "y": 675 }
},
{
"id": "api-1751871750000",
@@ -442,7 +589,7 @@
},
"type": "api",
"label": "Submit Document Verification API",
- "position": { "x": 720, "y": -105 }
+ "position": { "x": 1050, "y": 510 }
},
{
"id": "script-1751871770000",
@@ -520,7 +667,7 @@
},
"type": "script",
"label": "Process Verification Response",
- "position": { "x": 1020, "y": -105 }
+ "position": { "x": 630, "y": 525 }
},
{
"id": "gateway-1751871800000",
@@ -562,7 +709,7 @@
},
"type": "gateway",
"label": "Lengkap?",
- "position": { "x": 1125, "y": 195 }
+ "position": { "x": 1350, "y": 315 }
},
{
"id": "business-rule-1751871900000",
@@ -687,7 +834,7 @@
},
"type": "business-rule",
"label": "Analisis Had Kifayah",
- "position": { "x": 1485, "y": 45 }
+ "position": { "x": 1665, "y": 120 }
},
{
"id": "notification-1751871950000",
@@ -731,12 +878,281 @@
},
"type": "end",
"label": "End",
- "position": { "x": 1950, "y": 420 }
+ "position": { "x": 1935, "y": 390 }
+ },
+ {
+ "id": "html-1752109761532",
+ "data": {
+ "label": "Family Tree",
+ "shape": "rectangle",
+ "jsCode": "",
+ "cssCode": "",
+ "htmlCode": "\n\n
Custom HTML Content \n
This is a custom HTML node that can display rich content.
\n
",
+ "textColor": "#333333",
+ "autoRefresh": false,
+ "borderColor": "#dddddd",
+ "description": "Family Tree for Borang",
+ "inputVariables": [],
+ "backgroundColor": "#ffffff",
+ "outputVariables": [],
+ "allowVariableAccess": true
+ },
+ "type": "html",
+ "label": "Family Tree",
+ "position": { "x": 2385, "y": -360 }
+ },
+ {
+ "id": "rectangle-shape-1752110224921",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 650,
+ "height": 400,
+ "isShape": true,
+ "shapeType": "rectangle",
+ "textColor": "#374151",
+ "borderColor": "#16a34a",
+ "description": "",
+ "backgroundColor": "#e8f5e9"
+ },
+ "type": "rectangle-shape",
+ "label": "",
+ "position": { "x": 375, "y": -570 }
+ },
+ {
+ "id": "text-annotation-1752110279700",
+ "data": {
+ "label": "NF-NAS-PRF-AS-PA",
+ "shape": "rectangle",
+ "width": 200,
+ "height": 80,
+ "isShape": true,
+ "shapeType": "text-annotation",
+ "textColor": "#92400e",
+ "borderColor": "#fbbf24",
+ "description": "Pernilaian Awal",
+ "backgroundColor": "#fffbeb"
+ },
+ "type": "text-annotation",
+ "label": "BF-NAS-PRF-AS-PA",
+ "position": { "x": 810, "y": -555 }
+ },
+ {
+ "id": "rectangle-shape-1752110492897",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 650,
+ "height": 400,
+ "isShape": true,
+ "shapeType": "rectangle",
+ "textColor": "#374151",
+ "borderColor": "#16a34a",
+ "description": "",
+ "backgroundColor": "#e8f5e9"
+ },
+ "type": "rectangle-shape",
+ "label": "",
+ "position": { "x": 1185, "y": -570 }
+ },
+ {
+ "id": "text-annotation-1752110562983",
+ "data": {
+ "label": "BF-NAS-PRF-AS-QS-02",
+ "shape": "rectangle",
+ "width": 200,
+ "height": 80,
+ "isShape": true,
+ "shapeType": "text-annotation",
+ "textColor": "#92400e",
+ "borderColor": "#fbbf24",
+ "description": "Isi Borang Permohonan Online",
+ "backgroundColor": "#fffbeb"
+ },
+ "type": "text-annotation",
+ "label": "BF-NAS-PRF-AS-QS-02",
+ "position": { "x": 1620, "y": -555 }
+ },
+ {
+ "id": "rectangle-shape-1752114739551",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 650,
+ "height": 400,
+ "isShape": true,
+ "shapeType": "rectangle",
+ "textColor": "#374151",
+ "borderColor": "#16a34a",
+ "description": "",
+ "backgroundColor": "#e8f5e9"
+ },
+ "type": "rectangle-shape",
+ "label": "",
+ "position": { "x": 1995, "y": -570 }
+ },
+ {
+ "id": "api-1752114771983",
+ "data": {
+ "label": "API Call",
+ "shape": "rectangle",
+ "apiUrl": "",
+ "headers": "{ \"Content-Type\": \"application/json\" }",
+ "apiMethod": "GET",
+ "textColor": "#1e40af",
+ "borderColor": "#3b82f6",
+ "description": "External API call",
+ "requestBody": "",
+ "errorVariable": "apiError",
+ "outputVariable": "apiResponse",
+ "backgroundColor": "#eff6ff",
+ "continueOnError": false
+ },
+ "type": "api",
+ "label": "Called Family Tree",
+ "position": { "x": 2070, "y": -345 }
+ },
+ {
+ "id": "text-annotation-1752114833800",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 200,
+ "height": 80,
+ "isShape": true,
+ "shapeType": "text-annotation",
+ "textColor": "#92400e",
+ "borderColor": "#fbbf24",
+ "description": "Family Tree",
+ "backgroundColor": "#fffbeb"
+ },
+ "type": "text-annotation",
+ "label": "BF-NAS-PRF-AS-FM",
+ "position": { "x": 2430, "y": -555 }
+ },
+ {
+ "id": "form-1753000000000",
+ "data": {
+ "label": "Carian Profil",
+ "formId": 4,
+ "formName": "Carian Profil",
+ "formUuid": "4e07fc8f-a160-478a-85fd-fa3364401545",
+ "description": "Skrin carian asnaf atau login",
+ "assignedRoles": [],
+ "assignedUsers": [],
+ "inputMappings": [],
+ "assignmentType": "public",
+ "outputMappings": [
+ { "formField": "search_type", "processVariable": "carianSearchType" },
+ { "formField": "search_id", "processVariable": "carianSearchId" },
+ { "formField": "login_id", "processVariable": "carianLoginId" },
+ {
+ "formField": "login_password",
+ "processVariable": "carianLoginPassword"
+ }
+ ],
+ "fieldConditions": [],
+ "assignmentVariable": "",
+ "assignmentVariableType": "user_id"
+ },
+ "type": "form",
+ "label": "Carian Profil",
+ "position": { "x": 450, "y": -15 }
+ },
+ {
+ "id": "api-1753000000001",
+ "data": {
+ "label": "Submit Carian Profil API",
+ "apiUrl": "https://api.example.com/profiles/search",
+ "headers": "{ \"Content-Type\": \"application/json\" }",
+ "apiMethod": "POST",
+ "description": "Submit profile search or login credentials",
+ "requestBody": "{\n \"searchType\": \"{carianSearchType}\",\n \"searchId\": \"{carianSearchId}\",\n \"loginId\": \"{carianLoginId}\",\n \"password\": \"{carianLoginPassword}\"\n}",
+ "errorVariable": "carianProfilApiError",
+ "outputVariable": "carianProfilApiResponse",
+ "continueOnError": false
+ },
+ "type": "api",
+ "label": "Submit Carian Profil API",
+ "position": { "x": 450, "y": 180 }
+ },
+ {
+ "id": "script-1753000000002",
+ "data": {
+ "label": "Process Carian Profil Response",
+ "scriptCode": "// Process API response from profile search/login\nconst response = processVariables.carianProfilApiResponse;\n\nif (response && response.data) {\n if (response.data.loginSuccess) {\n processVariables.loginSuccess = true;\n processVariables.profileData = response.data.profile;\n processVariables.carianProfilStatus = 'login_successful';\n } else if (response.data.profileFound) {\n processVariables.profileFound = true;\n processVariables.profileData = response.data.profile;\n processVariables.carianProfilStatus = 'profile_found';\n } else {\n processVariables.profileFound = false;\n processVariables.loginSuccess = false;\n processVariables.carianProfilStatus = 'not_found';\n }\n} else {\n processVariables.carianProfilStatus = 'error';\n processVariables.carianProfilScriptError = 'Invalid or empty API response';\n}",
+ "description": "Process the response from the Carian Profil API",
+ "errorVariable": "carianProfilScriptError",
+ "inputVariables": ["carianProfilApiResponse"],
+ "scriptLanguage": "javascript",
+ "continueOnError": false,
+ "outputVariables": [
+ {
+ "name": "profileFound",
+ "type": "boolean",
+ "description": "Indicates if a profile was found via search"
+ },
+ {
+ "name": "loginSuccess",
+ "type": "boolean",
+ "description": "Indicates if the asnaf login was successful"
+ },
+ {
+ "name": "profileData",
+ "type": "object",
+ "description": "The retrieved profile data"
+ },
+ {
+ "name": "carianProfilStatus",
+ "type": "string",
+ "description": "The status of the profile search/login action"
+ }
+ ]
+ },
+ "type": "script",
+ "label": "Process Carian Profil Response",
+ "position": { "x": 780, "y": 180 }
+ },
+ {
+ "id": "rectangle-shape-1752115136908",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 650,
+ "height": 400,
+ "isShape": true,
+ "shapeType": "rectangle",
+ "textColor": "#374151",
+ "borderColor": "#16a34a",
+ "description": "",
+ "backgroundColor": "#e8f5e9"
+ },
+ "type": "rectangle-shape",
+ "label": "",
+ "position": { "x": 375, "y": -45 }
+ },
+ {
+ "id": "text-annotation-1752115184991",
+ "data": {
+ "label": "",
+ "shape": "rectangle",
+ "width": 200,
+ "height": 80,
+ "isShape": true,
+ "shapeType": "text-annotation",
+ "textColor": "#92400e",
+ "borderColor": "#fbbf24",
+ "description": "Carian Profil",
+ "backgroundColor": "#fffbeb"
+ },
+ "type": "text-annotation",
+ "label": "BF-NAS-PRF-AS-QS-01",
+ "position": { "x": 810, "y": -30 }
}
],
"viewport": {
- "x": -117.3519061583577,
- "y": 343.2355816226784,
- "zoom": 0.7722385141739979
+ "x": -104.1414298310864,
+ "y": 273.7689874210555,
+ "zoom": 0.402665859661672
}
}
diff --git a/docs/json/process-builder/processVariables.json b/docs/json/process-builder/processVariables.json
index 377bade..b007680 100644
--- a/docs/json/process-builder/processVariables.json
+++ b/docs/json/process-builder/processVariables.json
@@ -95,6 +95,13 @@
"value": "123456789012",
"description": "Bank account number from Section B"
},
+ "profileData": {
+ "name": "profileData",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "Data of the profile found"
+ },
"radioBangsa": {
"name": "radioBangsa",
"type": "string",
@@ -137,6 +144,13 @@
"value": "",
"description": "Birth certificate number from Section A"
},
+ "loginSuccess": {
+ "name": "loginSuccess",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Flag indicating successful login"
+ },
"okuAllowance": {
"name": "okuAllowance",
"type": "number",
@@ -151,6 +165,13 @@
"value": true,
"description": "Whether payment processing is ready"
},
+ "profileFound": {
+ "name": "profileFound",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Flag indicating if a profile was found"
+ },
"profileScore": {
"name": "profileScore",
"type": "number",
@@ -165,6 +186,20 @@
"value": "lelaki",
"description": "Gender from Section A"
},
+ "urgencyLevel": {
+ "name": "urgencyLevel",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Urgency level (high/medium/low)"
+ },
+ "urgencyScore": {
+ "name": "urgencyScore",
+ "type": "number",
+ "scope": "global",
+ "value": null,
+ "description": "Calculated urgency score based on needs"
+ },
"alamatSekolah": {
"name": "alamatSekolah",
"type": "string",
@@ -186,6 +221,13 @@
"value": "APP-1751871528249",
"description": "Generated application ID from script processing"
},
+ "carianLoginId": {
+ "name": "carianLoginId",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Asnaf ID for login"
+ },
"daerahSekolah": {
"name": "daerahSekolah",
"type": "string",
@@ -214,6 +256,13 @@
"value": true,
"description": "Whether applicant has dependents"
},
+ "hasOtherNeeds": {
+ "name": "hasOtherNeeds",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has other needs"
+ },
"householdType": {
"name": "householdType",
"type": "string",
@@ -270,6 +319,13 @@
"value": ["akaun"],
"description": "Payment method from Section B"
},
+ "carianSearchId": {
+ "name": "carianSearchId",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "IC or Foreign ID used for profile search"
+ },
"dateMasukislam": {
"name": "dateMasukislam",
"type": "string",
@@ -305,6 +361,34 @@
"value": null,
"description": "Risk assessment result from verification"
},
+ "catatanTambahan": {
+ "name": "catatanTambahan",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Catatan tambahan berkaitan permohonan"
+ },
+ "hasDisasterNeed": {
+ "name": "hasDisasterNeed",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has disaster-related needs"
+ },
+ "hasHomelessness": {
+ "name": "hasHomelessness",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant is homeless"
+ },
+ "penilaianAwalId": {
+ "name": "penilaianAwalId",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Generated ID for the initial assessment"
+ },
"radioPendidikan": {
"name": "radioPendidikan",
"type": "string",
@@ -340,6 +424,20 @@
"value": "melayu",
"description": "Dependent race from Section B"
},
+ "carianSearchType": {
+ "name": "carianSearchType",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Type of search for Carian Profil (e.g., 'ic_search', 'login')"
+ },
+ "dokumenBerkaitan": {
+ "name": "dokumenBerkaitan",
+ "type": "array",
+ "scope": "global",
+ "value": null,
+ "description": "Upload dokumen yang berkaitan"
+ },
"eligibilityScore": {
"name": "eligibilityScore",
"type": "number",
@@ -347,6 +445,13 @@
"value": null,
"description": "Eligibility score from verification API"
},
+ "hasNoUrgentNeeds": {
+ "name": "hasNoUrgentNeeds",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has no urgent needs"
+ },
"hubunganKeluarga": {
"name": "hubunganKeluarga",
"type": "array",
@@ -389,6 +494,20 @@
"value": true,
"description": "Whether documents verification is required"
},
+ "hasFamilyConflict": {
+ "name": "hasFamilyConflict",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has family conflict"
+ },
+ "hasUtilityArrears": {
+ "name": "hasUtilityArrears",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has utility arrears"
+ },
"jantinaTanggungan": {
"name": "jantinaTanggungan",
"type": "string",
@@ -396,6 +515,20 @@
"value": "perempuan",
"description": "Dependent gender from Section B"
},
+ "keperluanMendesak": {
+ "name": "keperluanMendesak",
+ "type": "array",
+ "scope": "global",
+ "value": null,
+ "description": "Apakah keperluan tuan/puan mendesak sekarang ini?"
+ },
+ "komitmenKosTinggi": {
+ "name": "komitmenKosTinggi",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Adakah tuan/puan mempunyai komitmen dan pembiayaan melibatkan kos yang tinggi?"
+ },
"namaPemegangAkaun": {
"name": "namaPemegangAkaun",
"type": "string",
@@ -431,6 +564,13 @@
"value": null,
"description": "Notes from document verification process"
},
+ "carianProfilStatus": {
+ "name": "carianProfilStatus",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Status of the Carian Profil step (e.g., 'found', 'not_found', 'login_success')"
+ },
"childcareAllowance": {
"name": "childcareAllowance",
"type": "number",
@@ -459,6 +599,13 @@
"value": "mykad",
"description": "Dependent ID type from Section B"
},
+ "penilaianAwalError": {
+ "name": "penilaianAwalError",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Error message from penilaian awal processing"
+ },
"verificationStatus": {
"name": "verificationStatus",
"type": "string",
@@ -473,6 +620,20 @@
"value": null,
"description": "Whether process can proceed to kifayah analysis"
},
+ "carianLoginPassword": {
+ "name": "carianLoginPassword",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Password for asnaf login"
+ },
+ "hasDeathRelatedNeed": {
+ "name": "hasDeathRelatedNeed",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has death-related needs"
+ },
"noTelefonTanggungan": {
"name": "noTelefonTanggungan",
"type": "string",
@@ -480,6 +641,13 @@
"value": "03-12345678",
"description": "Dependent phone number from Section B"
},
+ "penilaianAwalStatus": {
+ "name": "penilaianAwalStatus",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Status of initial assessment submission"
+ },
"spouseKifayahAmount": {
"name": "spouseKifayahAmount",
"type": "decimal",
@@ -501,6 +669,27 @@
"value": "ya",
"description": "Dependent currently studying from Section B"
},
+ "carianProfilApiError": {
+ "name": "carianProfilApiError",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "Error from Carian Profil API call"
+ },
+ "hasUploadedDocuments": {
+ "name": "hasUploadedDocuments",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether documents were uploaded"
+ },
+ "hasUrgentMedicalNeed": {
+ "name": "hasUrgentMedicalNeed",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has urgent medical needs"
+ },
"hubunganLainNyatakan": {
"name": "hubunganLainNyatakan",
"type": "string",
@@ -515,6 +704,13 @@
"value": null,
"description": "Error from kifayah analysis API call"
},
+ "readyForPersonalInfo": {
+ "name": "readyForPersonalInfo",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether ready for personal info form"
+ },
"verificationApiError": {
"name": "verificationApiError",
"type": "object",
@@ -522,6 +718,20 @@
"value": null,
"description": "Error from document verification API call"
},
+ "hasHighCostCommitment": {
+ "name": "hasHighCostCommitment",
+ "type": "boolean",
+ "scope": "global",
+ "value": null,
+ "description": "Whether applicant has high cost commitments"
+ },
+ "keperluanLainNyatakan": {
+ "name": "keperluanLainNyatakan",
+ "type": "string",
+ "scope": "global",
+ "value": null,
+ "description": "Keperluan lain yang dinyatakan oleh pemohon"
+ },
"kifayahAnalysisResult": {
"name": "kifayahAnalysisResult",
"type": "object",
@@ -529,6 +739,13 @@
"value": null,
"description": "Result from kifayah eligibility analysis API"
},
+ "penilaianAwalApiError": {
+ "name": "penilaianAwalApiError",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "API error from Submit Penilaian Awal API"
+ },
"tarikhLahirTanggungan": {
"name": "tarikhLahirTanggungan",
"type": "string",
@@ -585,6 +802,20 @@
"value": "ya",
"description": "Dependent lives with family from Section B"
},
+ "carianProfilApiResponse": {
+ "name": "carianProfilApiResponse",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "API response from Carian Profil submission"
+ },
+ "carianProfilScriptError": {
+ "name": "carianProfilScriptError",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "Error from Carian Profil script execution"
+ },
"chronicIllnessAllowance": {
"name": "chronicIllnessAllowance",
"type": "number",
@@ -662,6 +893,20 @@
"value": "",
"description": "Dependent other education specification from Section B"
},
+ "penilaianAwalApiResponse": {
+ "name": "penilaianAwalApiResponse",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "API response from Submit Penilaian Awal API"
+ },
+ "penilaianAwalScriptError": {
+ "name": "penilaianAwalScriptError",
+ "type": "object",
+ "scope": "global",
+ "value": null,
+ "description": "Error from Penilaian Awal script execution"
+ },
"tarikhMulaKfamTanggungan": {
"name": "tarikhMulaKfamTanggungan",
"type": "string",
diff --git a/pages/process-builder/index.vue b/pages/process-builder/index.vue
index 979b558..0d394f1 100644
--- a/pages/process-builder/index.vue
+++ b/pages/process-builder/index.vue
@@ -18,6 +18,8 @@ import BusinessRuleNodeConfiguration from '~/components/process-flow/BusinessRul
import BusinessRuleNodeConfigurationModal from '~/components/process-flow/BusinessRuleNodeConfigurationModal.vue';
import NotificationNodeConfigurationModal from '~/components/process-flow/NotificationNodeConfigurationModal.vue';
import ScriptNodeConfigurationModal from '~/components/process-flow/ScriptNodeConfigurationModal.vue';
+import HtmlNodeConfigurationModal from '~/components/process-flow/HtmlNodeConfigurationModal.vue';
+import SubprocessNodeConfigurationModal from '~/components/process-flow/SubprocessNodeConfigurationModal.vue';
import ProcessTemplatesModal from '~/components/ProcessTemplatesModal.vue';
import ProcessSettingsModal from '~/components/process-flow/ProcessSettingsModal.vue';
import ProcessHistoryModal from '~/components/ProcessHistoryModal.vue';
@@ -86,6 +88,8 @@ const showGatewayConfigModal = ref(false);
const showBusinessRuleConfigModal = ref(false);
const showNotificationConfigModal = ref(false);
const showScriptConfigModal = ref(false);
+const showHtmlConfigModal = ref(false);
+const showSubprocessConfigModal = ref(false);
const showTemplatesModal = ref(false);
const showProcessSettings = ref(false);
const showDropdown = ref(false);
@@ -252,6 +256,22 @@ const components = [
iconColor: 'text-gray-500',
data: { description: 'Script execution', language: 'JavaScript', shape: 'rectangle', backgroundColor: '#f9fafb', borderColor: '#6b7280', textColor: '#374151' }
},
+ {
+ type: 'html',
+ label: 'HTML',
+ icon: 'html',
+ iconColor: 'text-blue-500',
+ data: {
+ description: 'Custom HTML content',
+ htmlCode: '\n\n
Custom HTML Content \n
This is a custom HTML node that can display rich content.
\n
',
+ cssCode: '',
+ jsCode: '',
+ shape: 'rectangle',
+ backgroundColor: '#e0f2fe',
+ borderColor: '#0ea5e9',
+ textColor: '#0c4a6e'
+ }
+ },
{
type: 'business-rule',
label: 'Business Rule',
@@ -266,6 +286,21 @@ const components = [
iconColor: 'text-blue-500',
data: { description: 'Send notifications', shape: 'rectangle', backgroundColor: '#f0f9ff', borderColor: '#0ea5e9', textColor: '#0284c7' }
},
+ {
+ type: 'subprocess',
+ label: 'Sub Process',
+ icon: 'hub',
+ iconColor: 'text-teal-500',
+ data: {
+ description: 'Executes another process',
+ subprocessId: null,
+ subprocessName: '',
+ shape: 'rectangle',
+ backgroundColor: '#f0fdfa',
+ borderColor: '#14b8a6',
+ textColor: '#134e4a'
+ }
+ },
{
type: 'end',
label: 'End Point',
@@ -387,6 +422,7 @@ const getNodeIcon = (nodeType) => {
'script': 'code',
'business-rule': 'rule',
'notification': 'notifications',
+ 'subprocess': 'hub',
'start': 'play_circle_filled',
'end': 'stop_circle',
'swimlane-horizontal': 'view-stream',
@@ -530,7 +566,6 @@ const gatewayAvailableVariables = computed(() => {
description: v.description || ''
}));
- // console.log('Gateway available variables:', allVars);
return allVars;
});
@@ -607,6 +642,9 @@ const onNodeSelected = (node) => {
case 'notification':
defaultColors = { backgroundColor: '#f0f9ff', borderColor: '#0ea5e9', textColor: '#0284c7' };
break;
+ case 'subprocess':
+ defaultColors = { backgroundColor: '#f0fdfa', borderColor: '#14b8a6', textColor: '#134e4a' };
+ break;
}
if (!nodeCopy.data.backgroundColor) {
@@ -751,6 +789,9 @@ const resetNodeColors = () => {
case 'notification':
defaultColors = { backgroundColor: '#f0f9ff', borderColor: '#0ea5e9', textColor: '#0284c7' };
break;
+ case 'subprocess':
+ defaultColors = { backgroundColor: '#f0fdfa', borderColor: '#14b8a6', textColor: '#134e4a' };
+ break;
}
selectedNodeData.value.data.backgroundColor = defaultColors.backgroundColor;
@@ -802,16 +843,17 @@ const onNodesChange = (changes, currentNodes) => {
// Skip processing during component addition to avoid conflicts
if (isAddingComponent.value) {
- return;
+ return;
}
// Handle position changes (only when dragging is complete)
const positionChanges = {};
- const hasPositionChanges = changes
- .filter(change => change.type === 'position' && change.position && !change.dragging)
- .forEach(change => {
- positionChanges[change.id] = change.position;
- });
+ const positionChangesList = changes
+ .filter(change => change.type === 'position' && change.position && !change.dragging);
+
+ positionChangesList.forEach(change => {
+ positionChanges[change.id] = change.position;
+ });
if (Object.keys(positionChanges).length > 0) {
processStore.updateNodePositions(positionChanges);
@@ -833,6 +875,16 @@ const onNodesChange = (changes, currentNodes) => {
}
}
+ // Handle node additions (this should be rare since we add nodes through the store first)
+ const addedNodes = changes
+ .filter(change => change.type === 'add')
+ .map(change => change.id);
+
+ if (addedNodes.length > 0) {
+ // These nodes are already in the canvas, so we don't need to add them to the store
+ // unless they're not already there
+ }
+
// REMOVED: Don't overwrite selectedNodeData from canvas changes to preserve local edits
// This was causing property panel changes to be lost when the canvas updated
// The canvasNodes computed and its watcher will handle synchronization properly
@@ -842,6 +894,11 @@ const onNodesChange = (changes, currentNodes) => {
const onEdgesChange = (changes, currentEdges) => {
if (!changes || !currentEdges) return;
+ // Skip processing during component addition to avoid conflicts
+ if (isAddingComponent.value) {
+ return;
+ }
+
// Handle edge removals
const removedEdges = changes
.filter(change => change.type === 'remove')
@@ -872,7 +929,9 @@ const onEdgesChange = (changes, currentEdges) => {
label: edge.label || '',
type: edge.type || 'smoothstep',
animated: edge.animated !== undefined ? edge.animated : true,
- data: edge.data || {}
+ data: edge.data || {},
+ sourceHandle: edge.sourceHandle,
+ targetHandle: edge.targetHandle
});
}
});
@@ -1226,7 +1285,7 @@ const onAddComponent = async (component) => {
...component.data,
// Ensure shape is set for new nodes
shape: component.data.shape || (component.type === 'gateway' ? 'diamond' : 'rectangle'),
- // Ensure default colors are set for new nodes (use component defaults which are now type-specific)
+ // Ensure default colors are set for new nodes
backgroundColor: component.data.backgroundColor,
borderColor: component.data.borderColor,
textColor: component.data.textColor
@@ -1234,30 +1293,78 @@ const onAddComponent = async (component) => {
};
// Add the node to the process store
- processStore.addNode(newNode);
+ await processStore.addNode(newNode);
- // Wait for the next tick to ensure the store update is complete
+ // Wait for store update and next render cycle
await nextTick();
+ await new Promise(resolve => setTimeout(resolve, 50));
- // Explicitly sync the canvas with current store state
- if (processFlowCanvas.value && processFlowCanvas.value.syncCanvas) {
- processFlowCanvas.value.syncCanvas(
- processStore.currentProcess.nodes,
- processStore.currentProcess.edges
- );
+ // CRITICAL FIX: Instead of calling syncCanvas (which can cause edge removal/re-addition),
+ // we'll add the node directly to the canvas and preserve existing edges
+ if (processFlowCanvas.value) {
+ try {
+ // Get the fresh node from store (with any store-side modifications)
+ const freshNode = processStore.currentProcess?.nodes.find(n => n.id === newNode.id);
+
+ if (freshNode && processFlowCanvas.value.addNode) {
+ // Add only the new node to the canvas directly
+ processFlowCanvas.value.addNode(freshNode);
+
+ // Wait for the node to be added to the canvas
+ await nextTick();
+ await new Promise(resolve => setTimeout(resolve, 50));
+
+ // Select the newly added node after it's stable
+ onNodeSelected(freshNode);
+
+ console.log('โ
Successfully added new node without affecting existing edges');
+ } else {
+ console.warn('โ ๏ธ Fresh node not found in store, falling back to full sync');
+ // Fallback to full sync if something went wrong
+ const currentNodes = processStore.currentProcess?.nodes || [];
+ const currentEdges = processStore.currentProcess?.edges || [];
+
+ if (processFlowCanvas.value.syncCanvas) {
+ processFlowCanvas.value.syncCanvas(currentNodes, currentEdges);
+
+ await nextTick();
+ await new Promise(resolve => setTimeout(resolve, 50));
+
+ const addedNode = currentNodes.find(n => n.id === newNode.id);
+ if (addedNode) {
+ onNodeSelected(addedNode);
+ }
+ }
+ }
+ } catch (error) {
+ console.error('โ Error adding node to canvas:', error);
+
+ // Fallback to full sync if direct addition fails
+ const currentNodes = processStore.currentProcess?.nodes || [];
+ const currentEdges = processStore.currentProcess?.edges || [];
+
+ if (processFlowCanvas.value.syncCanvas) {
+ console.log('๐ Falling back to full canvas sync due to error');
+ processFlowCanvas.value.syncCanvas(currentNodes, currentEdges);
+
+ await nextTick();
+ await new Promise(resolve => setTimeout(resolve, 50));
+
+ const addedNode = currentNodes.find(n => n.id === newNode.id);
+ if (addedNode) {
+ onNodeSelected(addedNode);
+ }
+ }
+ }
}
-
- // Select the newly added node
- onNodeSelected(newNode);
-
-
} catch (error) {
console.error('Error adding component:', error);
+ toast.error('Failed to add component. Please try again.');
} finally {
- // Reset the flag after a short delay to allow canvas to stabilize
+ // Reset the flag after a longer delay to ensure canvas is stable
setTimeout(() => {
isAddingComponent.value = false;
- }, 100);
+ }, 200);
}
};
@@ -1498,6 +1605,61 @@ const handleScriptNodeUpdate = async (updatedData) => {
}
};
+// Handle HTML node update
+const handleHtmlNodeUpdate = async (updatedData) => {
+ if (selectedNodeData.value && selectedNodeData.value.type === 'html') {
+ // Make sure to update the label both in data and at the root level
+ const newLabel = updatedData.label || 'HTML Content';
+
+ // Update the data
+ selectedNodeData.value.data = {
+ ...updatedData,
+ label: newLabel // Ensure label is in data
+ };
+
+ // Also update the root label
+ selectedNodeData.value.label = newLabel;
+
+ // Add output variables to the process
+ if (updatedData.outputVariables && Array.isArray(updatedData.outputVariables)) {
+ updatedData.outputVariables.forEach(output => {
+ if (output.name && output.name.trim()) {
+ processStore.addProcessVariable({
+ name: output.name,
+ type: output.type || 'string',
+ scope: 'global',
+ value: null,
+ description: output.description || `Output from ${newLabel}`
+ });
+ }
+ });
+ }
+
+ // Update the node in store and refresh
+ await updateNodeInStore();
+ }
+};
+
+// Handle Sub-process node update
+const handleSubprocessNodeUpdate = async (updatedData) => {
+ if (selectedNodeData.value && selectedNodeData.value.type === 'subprocess') {
+ // Make sure to update the label both in data and at the root level
+ const newLabel = updatedData.label || 'Sub Process';
+
+ // Update the data
+ selectedNodeData.value.data = {
+ ...updatedData,
+ label: newLabel // Ensure label is in data
+ };
+
+ // Also update the root label
+ selectedNodeData.value.label = newLabel;
+
+ // Update the node in store and refresh
+ await updateNodeInStore();
+ }
+};
+
// Handle process restoration from history
const handleProcessRestored = (restoredProcess) => {
// The process has been restored in the backend, so we need to reload it
@@ -1618,6 +1780,51 @@ watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
}
}, 200); // Allow time for canvas to initialize
}, { immediate: false });
+
+// Duplicate Node logic
+const duplicateNode = async () => {
+ if (!selectedNodeData.value) return;
+ const node = selectedNodeData.value;
+ // Prevent duplication for start/end nodes and shapes
+ if (node.type === 'start' || node.type === 'end' || node.data?.isShape) return;
+
+ // Deep copy node
+ const newNode = JSON.parse(JSON.stringify(node));
+ newNode.id = `${node.type}_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
+ // Offset position
+ newNode.position = {
+ x: (node.position?.x || 100) + 40,
+ y: (node.position?.y || 100) + 40
+ };
+ // Update label
+ if (newNode.label) {
+ newNode.label = newNode.label.endsWith(' (Copy)') ? newNode.label : `${newNode.label} (Copy)`;
+ }
+ if (newNode.data && newNode.data.label) {
+ newNode.data.label = newNode.data.label.endsWith(' (Copy)') ? newNode.data.label : `${newNode.data.label} (Copy)`;
+ }
+ // Remove any edge/connection-specific data if present
+ delete newNode.selected;
+ delete newNode.dragging;
+
+ // Add to store only
+ await processStore.addNode(newNode);
+ await nextTick();
+ // Select the new node from the store
+ const freshNode = processStore.currentProcess?.nodes.find(n => n.id === newNode.id);
+ if (freshNode) {
+ onNodeSelected(freshNode);
+ }
+};
+
+// Add computed property for action visibility
+const canShowNodeActions = computed(() => {
+ return (
+ selectedNodeData.value &&
+ selectedNodeData.value.type !== 'start' &&
+ selectedNodeData.value.type !== 'end'
+ );
+});
@@ -1835,17 +2042,41 @@ watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
-
-
-
+
+
+
+
+
+
+
+ {{ selectedNodeData.data?.isShape ? 'Shape' : 'Node' }}: {{ selectedNodeData.type.replace('-', ' ') }}
+
+
{{ selectedNodeData.id }}
+
-
-
- {{ selectedNodeData.data?.isShape ? 'Shape' : 'Node' }}: {{ selectedNodeData.type.replace('-', ' ') }}
-
-
{{ selectedNodeData.id }}
+
+
+
+
+
+
+
@@ -2074,6 +2305,24 @@ watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
Configure Script Task
+
+
+
+
Configure HTML content and output variables.
+
+
+ Configure HTML Node
+
+
+
+
+
+
Select a process to execute as a sub-process.
+
+
+ Configure Sub-process
+
+
@@ -2233,6 +2482,25 @@ watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
:availableVariables="gatewayAvailableVariables"
@update="handleScriptNodeUpdate"
/>
+
+
+
+
+
+
processStore.currentProcess, async (newProcess, oldProcess) => {
@@ -3222,4 +3490,22 @@ watch(() => processStore.currentProcess, async (newProcess, oldProcess) => {
stroke-width: 3px; /* Thicker edges for easier mobile selection */
}
}
+
+/* Modern icon button style for node actions */
+.modern-icon-btn {
+ padding: 0.375rem;
+ border-radius: 9999px;
+ min-width: 32px;
+ min-height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.15s;
+}
+.modern-icon-btn:hover {
+ background: #f3f4f6;
+}
+.modern-icon-btn[variant~='danger-text']:hover {
+ background: #fee2e2;
+}
\ No newline at end of file
diff --git a/stores/processBuilder.js b/stores/processBuilder.js
index 5f33582..cd26d52 100644
--- a/stores/processBuilder.js
+++ b/stores/processBuilder.js
@@ -436,14 +436,7 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
y: flowData.viewport?.y || 0,
zoom: flowData.viewport?.zoom || 1
};
-
- console.log('๐งน Cleaned flow data:', {
- originalNodes: flowData.nodes?.length || 0,
- cleanNodes: cleanNodes.length,
- originalEdges: flowData.edges?.length || 0,
- cleanEdges: cleanEdges.length
- });
-
+
return {
nodes: cleanNodes,
edges: cleanEdges,
@@ -660,20 +653,37 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
return existingNode;
}
+ // Create a new node with proper data structure
const newNode = {
id: node.id || uuidv4(),
type: node.type,
- label: node.label || 'New Node',
- position: node.position || { x: 0, y: 0 },
- data: node.data || {}
+ label: node.label || node.data?.label || 'New Node',
+ position: node.position || { x: 100, y: 100 },
+ data: {
+ ...node.data,
+ label: node.data?.label || node.label || 'New Node',
+ // Ensure shape is set for new nodes
+ shape: node.data?.shape || (node.type === 'gateway' ? 'diamond' : 'rectangle'),
+ // Ensure default colors are set for new nodes
+ backgroundColor: node.data?.backgroundColor || '#ffffff',
+ borderColor: node.data?.borderColor || '#000000'
+ }
};
- this.currentProcess.nodes.push(newNode);
- this.selectedNodeId = newNode.id;
+ // Create a deep copy to avoid reference issues
+ const nodeCopy = JSON.parse(JSON.stringify(newNode));
+
+ // Add to current process nodes array
+ this.currentProcess.nodes = [...this.currentProcess.nodes, nodeCopy];
+
+ // Update selection
+ this.selectedNodeId = nodeCopy.id;
+
+ // Save to history
this.saveToHistory('Add node');
this.unsavedChanges = true;
- return newNode;
+ return nodeCopy;
},
/**