diff --git a/docs/json/process-builder/processDefinition.json b/docs/json/process-builder/processDefinition.json index 7594382..7c199c0 100644 --- a/docs/json/process-builder/processDefinition.json +++ b/docs/json/process-builder/processDefinition.json @@ -101,7 +101,8 @@ "inputMappings": [], "assignmentType": "roles", "outputMappings": [ - { "formField": "kategori_asnaf", "processVariable": "kategoriAsnaf" } + { "formField": "kategori_asnaf", "processVariable": "kategoriAsnaf" }, + { "formField": "nama_asnaf", "processVariable": "namaAsnaf" } ], "fieldConditions": [], "assignmentVariable": "", @@ -121,7 +122,10 @@ { "id": "api-1752550319410", "data": { - "body": { "data": "{ \"title\" : \"{todoTitle}\"}", "type": "raw" }, + "body": { + "data": "{ \"kategori_asnaf\" : \"{kategoriAsnaf}\", \"nama_asnaf\" : \"{namaAsnaf}\"}", + "type": "raw" + }, "label": "API Call", "shape": "rectangle", "apiUrl": "https://jsonplaceholder.typicode.com/posts", @@ -146,7 +150,7 @@ "id": "script-1752550430989", "data": { "label": "Script Task", - "scriptCode": "// Assign API response title to process variable\nprocessVariables.todoTitle = processVariables.apiResponse?.title || '';\n// You can add more logic here\n", + "scriptCode": "// Map API response to process variables\nconst api = processVariables.apiResponse || {};\nprocessVariables.todoTitle = api.kategori_asnaf || '';\nprocessVariables.namaAsnaf = api.nama_asnaf || '';\nprocessVariables.todoStatus = api.id > 100; // true if id > 100, otherwise false\n\n// New logic: Calculate a score\nconst katLen = (api.kategori_asnaf || '').length;\nconst namaLen = (api.nama_asnaf || '').length;\nprocessVariables.asnafScore = katLen * 10 + namaLen * 5;\n\n// New logic: Add a timestamp\nprocessVariables.resultTimestamp = new Date().toISOString();\n\n// New logic: Create a summary string\nprocessVariables.resultSummary = `Asnaf: ${processVariables.todoTitle}, Nama: ${processVariables.namaAsnaf}, Score: ${processVariables.asnafScore}, Time: ${processVariables.resultTimestamp}`;\n", "description": "Execute JavaScript code", "inputVariables": ["apiResponse"], "scriptLanguage": "javascript", @@ -155,6 +159,31 @@ "name": "todoTitle", "type": "string", "description": "Title from API response" + }, + { + "name": "namaAsnaf", + "type": "string", + "description": "Nama Asnaf from API response" + }, + { + "name": "todoStatus", + "type": "boolean", + "description": "Todo Status from API response" + }, + { + "name": "asnafScore", + "type": "number", + "description": "Calculated Asnaf Score" + }, + { + "name": "resultTimestamp", + "type": "string", + "description": "Result Timestamp" + }, + { + "name": "resultSummary", + "type": "string", + "description": "Result Summary" } ] }, @@ -166,12 +195,16 @@ "id": "html-1752550500000", "data": { "label": "Show Result", - "jsCode": "", - "cssCode": ".result-card { background: #f9fafb; border: 1px solid #ddd; border-radius: 8px; padding: 16px; max-width: 400px; margin: 24px auto; }", - "htmlCode": "
\n

API Result

\n

Todo Title: {{ processVariables.todoTitle }}

\n
", + "shape": "rectangle", + "jsCode": "const completed = \"{{todoStatus}}\" === 'true' || \"{{todoStatus}}\" === true;\ndocument.getElementById('todo-title').innerText = \"{{todoTitle}}\";\ndocument.getElementById('todo-status').innerText = completed ? 'Completed ✅' : 'Not Completed ❌';\ndocument.getElementById('todo-status').className = completed ? 'done' : 'not-done';", + "cssCode": ".result-box {\n background-color: #ffffff;\n border-radius: 12px;\n box-shadow: 0 8px 16px rgba(0,0,0,0.1);\n padding: 30px 40px;\n max-width: 600px;\n text-align: center;\n }\n .result-box h2 {\n color: #333;\n margin-bottom: 20px;\n }\n .result-box p {\n font-size: 1.2em;\n color: #555;\n }\n .done {\n color: green;\n font-weight: bold;\n }\n .not-done {\n color: red;\n font-weight: bold;\n }", + "htmlCode": "
\n

Asnaf Result

\n

Title: {{todoTitle}}

\n

Status: {{todoStatus}}

\n

Nama: {{namaAsnaf}}

\n

Score: {{asnafScore}}

\n

Timestamp: {{resultTimestamp}}

\n

Summary: {{resultSummary}}

\n
", + "textColor": "#333333", "autoRefresh": true, + "borderColor": "#dddddd", "description": "Display the todo title from API", - "inputVariables": ["todoTitle"], + "inputVariables": ["todoTitle", "todoStatus"], + "backgroundColor": "#ffffff", "outputVariables": [], "allowVariableAccess": true }, @@ -206,8 +239,8 @@ } ], "viewport": { - "x": -118.4524312896406, - "y": 314.4180761099366, - "zoom": 0.6437632135306554 + "x": -193.044397463002, + "y": 197.8681289640592, + "zoom": 1.049154334038055 } } diff --git a/docs/json/process-builder/processVariables.json b/docs/json/process-builder/processVariables.json index b59e613..ad70996 100644 --- a/docs/json/process-builder/processVariables.json +++ b/docs/json/process-builder/processVariables.json @@ -6,6 +6,18 @@ "value": null, "description": "API error from API Call" }, + "namaAsnaf": { + "name": "namaAsnaf", + "type": "string", + "scope": "global", + "description": "" + }, + "todoTitle": { + "name": "todoTitle", + "type": "string", + "scope": "global", + "description": "Title from API response" + }, "apiResponse": { "name": "apiResponse", "type": "object", @@ -19,10 +31,28 @@ "scope": "global", "description": "" }, - "todoTitle": { - "name": "todoTitle", + "todoStatus": { + "name": "todoStatus", + "type": "boolean", + "scope": "global", + "description": "Completion status (true if id > 100)" + }, + "asnafScore": { + "name": "asnafScore", + "type": "number", + "scope": "global", + "description": "Calculated score based on input lengths" + }, + "resultTimestamp": { + "name": "resultTimestamp", "type": "string", "scope": "global", - "description": "Title from API response" + "description": "Timestamp when script ran" + }, + "resultSummary": { + "name": "resultSummary", + "type": "string", + "scope": "global", + "description": "Summary string for result" } } diff --git a/navigation/index.js b/navigation/index.js index 7747978..034cfbd 100644 --- a/navigation/index.js +++ b/navigation/index.js @@ -12,6 +12,23 @@ export default [ }, ], }, + // { + // header: "BPM", + // description: "Manage your BPM application", + // child: [ + // { + // title: "Form", + // icon: "material-symbols:dynamic-form", + // path: "http://localhost:3000/workflow/7f024ce2-ce5d-43af-a18e-8e10d390e32b", + // external: true, + // }, + // ], + // meta: { + // auth: { + // role: ["Developer"], + // }, + // }, + // }, { header: "Design & Build", description: "Create and design your workflows and forms", diff --git a/pages/devtool/menu-editor/index.vue b/pages/devtool/menu-editor/index.vue index edcbce9..d8c3760 100644 --- a/pages/devtool/menu-editor/index.vue +++ b/pages/devtool/menu-editor/index.vue @@ -33,6 +33,7 @@ const showModalEditForm = ref({ name: "", path: "", guardType: "", + external: false, }); // const showModalEditEl = ref(null); @@ -41,6 +42,7 @@ const showModalAddForm = ref({ title: "", name: "", path: "", + external: false, }); const systemPages = [ @@ -120,6 +122,7 @@ const openModalEdit = (menu) => { showModalEditForm.value.path = menu.path; } + showModalEditForm.value.external = menu.external === true; showModalEditPath.value = menu.path; showModalEdit.value = true; @@ -140,6 +143,33 @@ const saveEditMenu = async () => { showModalEditForm.value.title = showModalEditForm.value.title.trim(); showModalEditForm.value.name = showModalEditForm.value.name.trim(); + // Path validation and formatting + let path = showModalEditForm.value.path.trim(); + if (showModalEditForm.value.external) { + // Validate as URL + if (!/^https?:\/\//.test(path)) { + nuxtApp.$swal.fire({ + title: "Error", + text: "External URL must start with http:// or https://", + icon: "error", + }); + return; + } + } else { + // Internal path validation + if (!/^[a-z0-9/-]+$/.test(path)) { + nuxtApp.$swal.fire({ + title: "Error", + text: "Path contains invalid characters or spacing before or after. Only letters, numbers, dashes, and underscores are allowed.", + icon: "error", + }); + return; + } + if (!path.startsWith("/")) { + path = "/" + path; + } + } + const res = await useFetch("/api/devtool/menu/edit", { method: "POST", initialCache: false, @@ -148,9 +178,9 @@ const saveEditMenu = async () => { formData: { title: showModalEditForm.value.title || "", name: showModalEditForm.value.name || "", - path: "/" + showModalEditForm.value.path || "", + path: path || "", + external: showModalEditForm.value.external === true, }, - // formData: showModalEditForm.value, }), }); @@ -175,6 +205,7 @@ const openModalAdd = () => { showModalAddForm.value.title = ""; showModalAddForm.value.name = ""; showModalAddForm.value.path = ""; + showModalAddForm.value.external = false; showModalAdd.value = true; }; @@ -194,6 +225,33 @@ const saveAddMenu = async () => { showModalAddForm.value.title = showModalAddForm.value.title.trim(); showModalAddForm.value.name = showModalAddForm.value.name.trim(); + // Path validation and formatting + let path = showModalAddForm.value.path.trim(); + if (showModalAddForm.value.external) { + // Validate as URL + if (!/^https?:\/\//.test(path)) { + nuxtApp.$swal.fire({ + title: "Error", + text: "External URL must start with http:// or https://", + icon: "error", + }); + return; + } + } else { + // Internal path validation + if (!/^[a-z0-9/-]+$/.test(path)) { + nuxtApp.$swal.fire({ + title: "Error", + text: "Path contains invalid characters or spacing before or after. Only letters, numbers, dashes, and underscores are allowed.", + icon: "error", + }); + return; + } + if (!path.startsWith("/")) { + path = "/" + path; + } + } + const res = await useFetch("/api/devtool/menu/add", { method: "POST", initialCache: false, @@ -201,9 +259,9 @@ const saveAddMenu = async () => { formData: { title: showModalAddForm.value.title || "", name: showModalAddForm.value.name || "", - path: "/" + showModalAddForm.value.path || "", + path: path || "", + external: showModalAddForm.value.external === true, }, - // formData: showModalAddForm.value }), }); @@ -691,8 +749,8 @@ watch( }" v-model="showModalEditForm.title" /> - + +
Cancel @@ -742,8 +818,8 @@ watch( }" v-model="showModalAddForm.title" /> - + +
Cancel diff --git a/pages/process-builder/manage.vue b/pages/process-builder/manage.vue index c950cee..ef9f909 100644 --- a/pages/process-builder/manage.vue +++ b/pages/process-builder/manage.vue @@ -2,6 +2,7 @@ import { ref, computed, onMounted, watch, onUnmounted } from 'vue'; import { useProcessBuilderStore } from '~/stores/processBuilder'; import { useRouter } from 'vue-router'; +import { useToast } from '~/composables/useToast'; // Define page meta definePageMeta({ @@ -15,6 +16,7 @@ definePageMeta({ // Initialize the store and router const processStore = useProcessBuilderStore(); const router = useRouter(); +const toast = useToast(); // State const searchQuery = ref(''); @@ -334,6 +336,17 @@ onMounted(async () => { onUnmounted(() => { clearTimeout(searchTimeout); }); + +// Copy workflow run link to clipboard +const copyWorkflowLink = async (processId) => { + try { + const link = `${window.location.origin}/workflow/${processId}`; + await navigator.clipboard.writeText(link); + toast.success('Run link copied to clipboard!'); + } catch (err) { + toast.error('Failed to copy link'); + } +};