Compare commits
No commits in common. "99b2e43cfe169b2ddb59c471f3b32cb7e9605be4" and "e10a3745c0ca1076e82a140e10c2325dca6d1d3a" have entirely different histories.
99b2e43cfe
...
e10a3745c0
@ -473,14 +473,14 @@
|
|||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<!-- User Dropdown -->
|
<!-- User Dropdown -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<FormKit
|
<FormKit
|
||||||
type="select"
|
type="select"
|
||||||
v-model="selectedUserId"
|
v-model="selectedUserId"
|
||||||
:options="filteredAvailableUsers"
|
:options="filteredAvailableUsers"
|
||||||
placeholder="Select a user to add..."
|
placeholder="Select a user to add..."
|
||||||
:classes="{ outer: 'mb-0' }"
|
:classes="{ outer: 'mb-0' }"
|
||||||
@input="handleUserSelection"
|
@input="handleUserSelection"
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-xs text-blue-700">Select users who will be able to complete this form task</p>
|
<p class="mt-1 text-xs text-blue-700">Select users who will be able to complete this form task</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -510,14 +510,14 @@
|
|||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<!-- Role Dropdown -->
|
<!-- Role Dropdown -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<FormKit
|
<FormKit
|
||||||
type="select"
|
type="select"
|
||||||
v-model="selectedRoleId"
|
v-model="selectedRoleId"
|
||||||
:options="filteredAvailableRoles"
|
:options="filteredAvailableRoles"
|
||||||
placeholder="Select a role to add..."
|
placeholder="Select a role to add..."
|
||||||
:classes="{ outer: 'mb-0' }"
|
:classes="{ outer: 'mb-0' }"
|
||||||
@input="handleRoleSelection"
|
@input="handleRoleSelection"
|
||||||
/>
|
/>
|
||||||
<p class="mt-1 text-xs text-purple-700">Select roles that will be able to complete this form task</p>
|
<p class="mt-1 text-xs text-purple-700">Select roles that will be able to complete this form task</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -904,11 +904,11 @@ const filteredAvailableUsers = computed(() => {
|
|||||||
return users.value
|
return users.value
|
||||||
.filter(user => !selectedUserIds.includes(String(user.userID)))
|
.filter(user => !selectedUserIds.includes(String(user.userID)))
|
||||||
.map(user => ({
|
.map(user => ({
|
||||||
label: user.userFullName ? `${user.userFullName} (${user.userUsername})` : user.userUsername,
|
label: user.userFullName ? `${user.userFullName} (${user.userUsername})` : user.userUsername,
|
||||||
value: String(user.userID), // Ensure value is a string
|
value: String(user.userID), // Ensure value is a string
|
||||||
username: user.userUsername,
|
username: user.userUsername,
|
||||||
email: user.userEmail
|
email: user.userEmail
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Computed property for available roles with filtering out already selected roles
|
// Computed property for available roles with filtering out already selected roles
|
||||||
@ -919,10 +919,10 @@ const filteredAvailableRoles = computed(() => {
|
|||||||
return roles.value
|
return roles.value
|
||||||
.filter(role => !selectedRoleIds.includes(String(role.roleID)))
|
.filter(role => !selectedRoleIds.includes(String(role.roleID)))
|
||||||
.map(role => ({
|
.map(role => ({
|
||||||
label: role.roleName,
|
label: role.roleName,
|
||||||
value: String(role.roleID), // Ensure value is a string
|
value: String(role.roleID), // Ensure value is a string
|
||||||
description: role.roleDescription
|
description: role.roleDescription
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch users and roles data
|
// Fetch users and roles data
|
||||||
@ -1046,7 +1046,7 @@ function handleUserSelection(userId) {
|
|||||||
selectedUserId.value = '';
|
selectedUserId.value = '';
|
||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
saveChanges();
|
saveChanges();
|
||||||
} else {
|
} else {
|
||||||
console.warn('Selected user not found in filtered available users', userIdStr);
|
console.warn('Selected user not found in filtered available users', userIdStr);
|
||||||
|
|
||||||
|
@ -54,9 +54,9 @@ const {
|
|||||||
deleteKeyCode: 'Delete',
|
deleteKeyCode: 'Delete',
|
||||||
selectionKeyCode: 'Shift',
|
selectionKeyCode: 'Shift',
|
||||||
multiSelectionKeyCode: 'Control',
|
multiSelectionKeyCode: 'Control',
|
||||||
connectionMode: 'strict',
|
connectionMode: 'loose',
|
||||||
isValidConnection: (connection) => {
|
isValidConnection: (connection) => {
|
||||||
console.log('Validating connection:', connection);
|
// console.log('Validating connection:', connection);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -76,7 +76,7 @@ const flowOptions = ref({
|
|||||||
snapToGrid: true,
|
snapToGrid: true,
|
||||||
snapGrid: [15, 15],
|
snapGrid: [15, 15],
|
||||||
edgeUpdaterRadius: 10,
|
edgeUpdaterRadius: 10,
|
||||||
connectionMode: 'strict',
|
connectionMode: 'loose',
|
||||||
connectionRadius: 25,
|
connectionRadius: 25,
|
||||||
elevateEdgesOnSelect: true,
|
elevateEdgesOnSelect: true,
|
||||||
nodesDraggable: true,
|
nodesDraggable: true,
|
||||||
@ -282,7 +282,7 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
|
|||||||
if (edgesToAdd.length > 0) {
|
if (edgesToAdd.length > 0) {
|
||||||
// Ensure all edges have proper handle specifications
|
// Ensure all edges have proper handle specifications
|
||||||
const edgesWithHandles = edgesToAdd.map(edge => {
|
const edgesWithHandles = edgesToAdd.map(edge => {
|
||||||
// IMPORTANT: If edge already has sourceHandle and targetHandle, preserve them exactly as they are
|
// If edge already has sourceHandle and targetHandle, use them
|
||||||
if (edge.sourceHandle && edge.targetHandle) {
|
if (edge.sourceHandle && edge.targetHandle) {
|
||||||
return edge;
|
return edge;
|
||||||
}
|
}
|
||||||
@ -325,22 +325,23 @@ watch(() => [props.initialEdges, nodes.value.length], async ([newEdges, nodeCoun
|
|||||||
// console.log('ProcessFlowCanvas: Successfully added edges with handles:', edgesWithHandles.length);
|
// console.log('ProcessFlowCanvas: Successfully added edges with handles:', edgesWithHandles.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing edges that have changed - IMPORTANT: preserve handle positions
|
// Update existing edges that have changed
|
||||||
newEdges.forEach(newEdge => {
|
newEdges.forEach(newEdge => {
|
||||||
const existingEdge = edges.value.find(e => e.id === newEdge.id);
|
const existingEdge = edges.value.find(e => e.id === newEdge.id);
|
||||||
if (existingEdge) {
|
if (existingEdge) {
|
||||||
// Check if the edge has actually changed before updating
|
// Check if the edge has actually changed before updating
|
||||||
const hasChanges = (
|
const hasChanges = (
|
||||||
existingEdge.label !== newEdge.label ||
|
existingEdge.label !== newEdge.label ||
|
||||||
|
existingEdge.sourceHandle !== newEdge.sourceHandle ||
|
||||||
|
existingEdge.targetHandle !== newEdge.targetHandle ||
|
||||||
JSON.stringify(existingEdge.style) !== JSON.stringify(newEdge.style)
|
JSON.stringify(existingEdge.style) !== JSON.stringify(newEdge.style)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
Object.assign(existingEdge, {
|
Object.assign(existingEdge, {
|
||||||
label: newEdge.label,
|
label: newEdge.label,
|
||||||
// Preserve existing handles if they exist
|
sourceHandle: newEdge.sourceHandle,
|
||||||
sourceHandle: existingEdge.sourceHandle || newEdge.sourceHandle,
|
targetHandle: newEdge.targetHandle,
|
||||||
targetHandle: existingEdge.targetHandle || newEdge.targetHandle,
|
|
||||||
style: newEdge.style ? { ...newEdge.style } : undefined
|
style: newEdge.style ? { ...newEdge.style } : undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
113
error.vue
113
error.vue
@ -1,85 +1,68 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: "Error Page",
|
title: "Error Page",
|
||||||
layout: "empty",
|
layout: "empty",
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
error: {
|
error: Object,
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log("props", props);
|
// console.log("props", props);
|
||||||
|
|
||||||
const isDynamicRouteError = computed(() => {
|
const redirectClearError = () => {
|
||||||
const path = window?.location?.pathname || '';
|
clearError({ redirect: "/" });
|
||||||
return path.includes('/[') || path.includes(']');
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
if (props.error.statusCode === 404 || isDynamicRouteError.value) {
|
|
||||||
return navigateTo('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
clearError({ redirect: window.location.pathname });
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 flex flex-col items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
<div>
|
||||||
<div class="max-w-md w-full space-y-8">
|
<div
|
||||||
<div>
|
class="flex h-screen p-6 md:p-10"
|
||||||
<img src="@/assets/img/logo/logo-word-black.svg" alt="Corrad Logo" class="h-12 mx-auto" />
|
v-if="props.error.statusCode === 404"
|
||||||
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
>
|
||||||
{{ error.statusCode === 404 ? 'Page not found' : 'An error occurred' }}
|
<div class="m-auto">
|
||||||
</h2>
|
<div class="flex items-center flex-col md:flex-row gap-10">
|
||||||
<p class="mt-2 text-center text-sm text-gray-600">
|
<img
|
||||||
{{ error.message }}
|
class="w-80 flex-shrink"
|
||||||
</p>
|
src="@/assets/img/illustration/404-2.svg"
|
||||||
</div>
|
alt=""
|
||||||
|
/>
|
||||||
<div class="bg-white p-6 rounded-lg shadow-md">
|
<div class="flex-1 text-center md:text-left items-center">
|
||||||
<div v-if="isDynamicRouteError" class="mb-6">
|
<span class="block mb-2 font-bold text-2xl md:text-3xl">Oops!</span>
|
||||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Invalid URL Format</h3>
|
<p class="text-lg md:text-xl font-base">
|
||||||
<p class="text-gray-600 mb-4">
|
The page you are looking for does not exist.
|
||||||
It looks like you're trying to access a page with a placeholder in the URL.
|
</p>
|
||||||
Dynamic routes like <code class="bg-gray-100 px-1 py-0.5 rounded">[id]</code> need to be
|
|
||||||
replaced with actual values.
|
<button
|
||||||
</p>
|
@click="redirectClearError"
|
||||||
<div class="bg-blue-50 border-l-4 border-blue-400 p-4">
|
class="mt-5 w-fit rounded-lg flex justify-center items-center h-fit text-sm px-8 py-2.5 text-white bg-[#212E3B] hover:bg-[#212E3B]/90 disabled:bg-[#212E3B]/30 disabled:text-[#212E3B]/50 disabled:border-primary/5 disabled:cursor-default"
|
||||||
<div class="flex">
|
>
|
||||||
<div class="flex-shrink-0">
|
Back to Home
|
||||||
<Icon name="heroicons:information-circle" class="h-5 w-5 text-blue-400" />
|
</button>
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<p class="text-sm text-blue-700">
|
|
||||||
Instead of using <strong>/execution/form/[id]</strong>, you should use a specific ID, like
|
|
||||||
<strong>/execution/form/123</strong> or navigate from the case list page.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex flex-col space-y-4">
|
</div>
|
||||||
<button
|
<div
|
||||||
@click="handleError"
|
class="flex h-screen p-6 md:p-10"
|
||||||
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
v-else-if="props.error.statusCode === 500"
|
||||||
>
|
>
|
||||||
{{ error.statusCode === 404 ? 'Go to homepage' : 'Try again' }}
|
<div class="m-auto">
|
||||||
</button>
|
<div class="flex items-center flex-col md:flex-row gap-10">
|
||||||
|
<img
|
||||||
<button
|
class="w-80 flex-shrink"
|
||||||
v-if="error.statusCode !== 404"
|
src="@/assets/img/illustration/500.svg"
|
||||||
@click="navigateTo('/')"
|
alt=""
|
||||||
class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
/>
|
||||||
>
|
<div class="flex-1 text-center md:text-left items-center">
|
||||||
Go to homepage
|
<span class="block mb-2 font-bold text-2xl md:text-3xl"
|
||||||
</button>
|
>Oops, something went wrong.
|
||||||
|
</span>
|
||||||
|
<p class="text-lg md:text-xl font-base">
|
||||||
|
Please try again later or contact us if the problem persists.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,360 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container mx-auto px-4 py-6">
|
|
||||||
<p class="text-lg font-bold mb-4">{{ caseInstance.caseName }}</p>
|
|
||||||
|
|
||||||
<div v-if="loading" class="text-blue-500">Loading...</div>
|
|
||||||
<div v-if="error" class="text-red-500">{{ error }}</div>
|
|
||||||
|
|
||||||
<div v-if="forms.length > 0">
|
|
||||||
<!-- Tab Navigation -->
|
|
||||||
<div class="border-b border-gray-200 mb-6">
|
|
||||||
<nav class="flex -mb-px">
|
|
||||||
<button
|
|
||||||
v-for="(form, index) in forms"
|
|
||||||
:key="index"
|
|
||||||
@click="activeTabIndex = index"
|
|
||||||
class="py-4 px-6 font-medium text-sm border-b-2 whitespace-nowrap"
|
|
||||||
:class="[
|
|
||||||
activeTabIndex === index
|
|
||||||
? 'border-primary text-primary'
|
|
||||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="w-6 h-6 rounded-full bg-gray-200 flex items-center justify-center mr-2"
|
|
||||||
:class="{ 'bg-primary text-white': activeTabIndex === index }">
|
|
||||||
{{ index + 1 }}
|
|
||||||
</div>
|
|
||||||
{{ form.formName || `Form ${index + 1}` }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab Content -->
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<div v-for="(form, index) in forms" :key="`content-${index}`" v-show="activeTabIndex === index">
|
|
||||||
<h2 class="text-xl font-semibold mb-4">{{ form.formName || `Form ${index + 1}` }}</h2>
|
|
||||||
<p class="text-gray-600 mb-4">{{ form.description || 'Please complete this form step' }}</p>
|
|
||||||
|
|
||||||
<!-- Form content -->
|
|
||||||
<FormKit
|
|
||||||
type="form"
|
|
||||||
:id="`form-${form.formID}`"
|
|
||||||
v-model="formData[index]"
|
|
||||||
@submit="handleSubmit(index)"
|
|
||||||
:actions="false"
|
|
||||||
:incomplete-message="false"
|
|
||||||
validation-visibility="submit"
|
|
||||||
>
|
|
||||||
<div class="grid-preview-container">
|
|
||||||
<template v-if="form.formComponents && form.formComponents.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="(component, compIndex) in form.formComponents"
|
|
||||||
:key="`component-${compIndex}`"
|
|
||||||
:style="{
|
|
||||||
gridColumn: component.props?.gridColumn || 'span 12'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- Standard FormKit inputs -->
|
|
||||||
<FormKit
|
|
||||||
v-if="isStandardInput(component.type)"
|
|
||||||
:type="component.type"
|
|
||||||
:name="component.props?.name"
|
|
||||||
:label="component.props?.label"
|
|
||||||
:help="component.props?.help"
|
|
||||||
:placeholder="component.props?.placeholder"
|
|
||||||
:validation="component.props?.validation"
|
|
||||||
:options="component.props?.options"
|
|
||||||
:value="component.props?.value"
|
|
||||||
:class="component.props?.width ? `w-${component.props.width}` : 'w-full'"
|
|
||||||
class="mb-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Heading -->
|
|
||||||
<div v-else-if="component.type === 'heading'" class="py-2 mb-4">
|
|
||||||
<component
|
|
||||||
:is="`h${component.props?.level || 2}`"
|
|
||||||
class="font-semibold"
|
|
||||||
:class="{
|
|
||||||
'text-2xl': component.props?.level === 2,
|
|
||||||
'text-xl': component.props?.level === 3,
|
|
||||||
'text-lg': component.props?.level === 4
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ component.props?.value || 'Heading Text' }}
|
|
||||||
</component>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Paragraph -->
|
|
||||||
<div v-else-if="component.type === 'paragraph'" class="py-2 mb-4">
|
|
||||||
<p class="text-gray-600">{{ component.props?.value || 'Paragraph text goes here' }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Divider -->
|
|
||||||
<div v-else-if="component.type === 'divider'" class="py-2 mb-4">
|
|
||||||
<hr class="border-t border-gray-200">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Info Display -->
|
|
||||||
<div v-else-if="component.type === 'info-display'" class="mb-4">
|
|
||||||
<div
|
|
||||||
class="p-4 rounded-lg"
|
|
||||||
:class="{ 'border': component.props?.showBorder }"
|
|
||||||
:style="{ backgroundColor: component.props?.backgroundColor || '#f8fafc' }"
|
|
||||||
>
|
|
||||||
<h3 class="font-medium mb-2">{{ component.props?.title || 'Information' }}</h3>
|
|
||||||
<div
|
|
||||||
:class="{
|
|
||||||
'grid grid-cols-2 gap-4': component.props?.layout === 'grid',
|
|
||||||
'flex flex-col space-y-2': component.props?.layout === 'vertical' || !component.props?.layout,
|
|
||||||
'flex flex-row flex-wrap gap-4': component.props?.layout === 'horizontal'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(field, fieldIndex) in component.props?.fields"
|
|
||||||
:key="`field-${fieldIndex}`"
|
|
||||||
class="text-sm"
|
|
||||||
>
|
|
||||||
<span class="text-gray-600">{{ field.label }}:</span>
|
|
||||||
<span class="ml-2 font-medium">{{ field.value }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Button -->
|
|
||||||
<div v-else-if="component.type === 'button'" class="py-2 mb-4">
|
|
||||||
<RsButton
|
|
||||||
:type="component.props?.buttonType || 'button'"
|
|
||||||
:variant="component.props?.variant || 'primary'"
|
|
||||||
:size="component.props?.size || 'md'"
|
|
||||||
:disabled="component.props?.disabled || false"
|
|
||||||
>
|
|
||||||
{{ component.props?.label || 'Button' }}
|
|
||||||
</RsButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div v-else class="text-center py-8 text-gray-500">
|
|
||||||
No form components found.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit button if not already included in the form -->
|
|
||||||
<!-- <FormKit
|
|
||||||
v-if="!hasSubmitButton(form)"
|
|
||||||
type="submit"
|
|
||||||
label="Submit"
|
|
||||||
:disabled="submitting"
|
|
||||||
:classes="{
|
|
||||||
input: submitting ? 'opacity-75 cursor-wait' : ''
|
|
||||||
}"
|
|
||||||
class="mt-6"
|
|
||||||
/> -->
|
|
||||||
<div v-if="submitting" class="text-center mt-2 text-sm text-blue-500">
|
|
||||||
Submitting form...
|
|
||||||
</div>
|
|
||||||
</FormKit>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation Buttons -->
|
|
||||||
<div class="flex justify-between mt-6">
|
|
||||||
<button
|
|
||||||
@click="prevStep"
|
|
||||||
class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
|
||||||
:disabled="activeTabIndex === 0"
|
|
||||||
:class="{ 'opacity-50 cursor-not-allowed': activeTabIndex === 0 }"
|
|
||||||
>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="nextStep"
|
|
||||||
class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary-dark"
|
|
||||||
:disabled="activeTabIndex === forms.length - 1"
|
|
||||||
:class="{ 'opacity-50 cursor-not-allowed': activeTabIndex === forms.length - 1 }"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="!loading && !error">
|
|
||||||
<p class="text-gray-500">No forms available.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { onMounted, ref, computed } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import RsButton from '~/components/RsButton.vue'
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref(null)
|
|
||||||
const route = useRoute()
|
|
||||||
const forms = ref([])
|
|
||||||
const caseInstance = ref([])
|
|
||||||
const activeTabIndex = ref(0)
|
|
||||||
const formData = ref([])
|
|
||||||
const submitting = ref(false)
|
|
||||||
|
|
||||||
// Computed property for current form
|
|
||||||
const currentForm = computed(() => {
|
|
||||||
return forms.value[activeTabIndex.value] || null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Navigation methods
|
|
||||||
const nextStep = () => {
|
|
||||||
if (activeTabIndex.value < forms.value.length - 1) {
|
|
||||||
activeTabIndex.value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prevStep = () => {
|
|
||||||
if (activeTabIndex.value > 0) {
|
|
||||||
activeTabIndex.value--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if component type is a standard FormKit input
|
|
||||||
const isStandardInput = (type) => {
|
|
||||||
const standardInputs = [
|
|
||||||
'text', 'email', 'password', 'number', 'tel', 'url',
|
|
||||||
'textarea', 'select', 'checkbox', 'radio', 'date',
|
|
||||||
'time', 'datetime-local', 'file', 'color', 'range',
|
|
||||||
'otp', 'mask', 'dropzone', 'switch'
|
|
||||||
]
|
|
||||||
return standardInputs.includes(type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if form has a submit button component
|
|
||||||
const hasSubmitButton = (form) => {
|
|
||||||
if (!form.formComponents) return false
|
|
||||||
return form.formComponents.some(comp =>
|
|
||||||
comp.type === 'button' &&
|
|
||||||
comp.props?.buttonType === 'submit'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
const handleSubmit = async (formIndex) => {
|
|
||||||
try {
|
|
||||||
submitting.value = true
|
|
||||||
console.log(`Form ${formIndex + 1} submitted:`, formData.value[formIndex])
|
|
||||||
|
|
||||||
// Example submission logic - replace with your actual API endpoint
|
|
||||||
const response = await $fetch(`/api/cases/${route.params.id}/forms/${forms.value[formIndex].formID}/submit`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
formData: formData.value[formIndex]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
// Move to next form if available
|
|
||||||
if (formIndex < forms.value.length - 1) {
|
|
||||||
activeTabIndex.value = formIndex + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(response.error || 'Form submission failed')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error submitting form:', err)
|
|
||||||
error.value = err.message || 'Failed to submit form'
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
const fetchCaseInstance = async (caseId) => {
|
|
||||||
try {
|
|
||||||
loading.value = true;
|
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
// Fetch case instance and all related forms using the API endpoint
|
|
||||||
const response = await $fetch(`/api/cases/${caseId}/forms`);
|
|
||||||
if (!response.success) {
|
|
||||||
throw new Error(response.error || 'Failed to load case instance and forms');
|
|
||||||
} else {
|
|
||||||
caseInstance.value = response.caseInstance
|
|
||||||
forms.value = response.forms
|
|
||||||
console.log(response.forms)
|
|
||||||
|
|
||||||
// Initialize formData array with empty objects for each form
|
|
||||||
formData.value = forms.value.map(() => ({}))
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error fetching case instance and forms:', err);
|
|
||||||
error.value = err.message || 'Failed to load forms';
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lifecycle hooks
|
|
||||||
onMounted(() => {
|
|
||||||
const caseId = route.params.id;
|
|
||||||
if (caseId) {
|
|
||||||
fetchCaseInstance(caseId);
|
|
||||||
} else {
|
|
||||||
error.value = 'No case ID provided';
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.grid-preview-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(12, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-preview-container > div {
|
|
||||||
grid-column: span 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply width classes */
|
|
||||||
.w-25 { width: 25%; }
|
|
||||||
.w-33 { width: 33.333%; }
|
|
||||||
.w-50 { width: 50%; }
|
|
||||||
.w-66 { width: 66.666%; }
|
|
||||||
.w-75 { width: 75%; }
|
|
||||||
.w-100 { width: 100%; }
|
|
||||||
|
|
||||||
/* Match form-builder styling */
|
|
||||||
:deep(.formkit-outer) {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.formkit-label) {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #374151;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.formkit-input) {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.formkit-input:focus) {
|
|
||||||
outline: none;
|
|
||||||
border-color: #3b82f6;
|
|
||||||
box-shadow: 0 0 0 1px #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.formkit-help) {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -26,24 +26,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</rs-card>
|
</rs-card>
|
||||||
|
|
||||||
<!-- Loading State -->
|
|
||||||
<div v-if="loading" class="flex justify-center items-center py-12">
|
|
||||||
<rs-progress-bar :indeterminate="true" class="w-64" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Error State -->
|
|
||||||
<rs-alert v-if="error" variant="danger" class="mb-6">
|
|
||||||
{{ error }}
|
|
||||||
</rs-alert>
|
|
||||||
|
|
||||||
<!-- Process Grid -->
|
<!-- Process Grid -->
|
||||||
<div v-if="!loading" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<rs-card
|
<rs-card
|
||||||
v-for="process in filteredProcesses"
|
v-for="process in filteredProcesses"
|
||||||
:key="process.id"
|
:key="process.id"
|
||||||
class="overflow-hidden hover:shadow-md transition-shadow duration-300"
|
class="overflow-hidden hover:shadow-md transition-shadow duration-300"
|
||||||
>
|
>
|
||||||
<div :class="`h-3 bg-${getProcessColor(process.category)}-500`"></div>
|
<div :class="`h-3 bg-${process.color}-500`"></div>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<div class="flex justify-between items-start mb-4">
|
<div class="flex justify-between items-start mb-4">
|
||||||
@ -67,14 +57,14 @@
|
|||||||
class="text-base mr-1"
|
class="text-base mr-1"
|
||||||
name="material-symbols:schedule"
|
name="material-symbols:schedule"
|
||||||
></Icon>
|
></Icon>
|
||||||
<span>Created: {{ formatDate(process.createdAt) }}</span>
|
<span>Average duration: {{ process.duration }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Icon
|
<Icon
|
||||||
class="text-base mr-1"
|
class="text-base mr-1"
|
||||||
name="material-symbols:sync"
|
name="material-symbols:sync"
|
||||||
></Icon>
|
></Icon>
|
||||||
<span>Status: {{ process.status }}</span>
|
<span>{{ process.steps }} steps</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
@ -89,21 +79,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</rs-card>
|
</rs-card>
|
||||||
|
|
||||||
<!-- Empty State -->
|
|
||||||
<div v-if="filteredProcesses.length === 0 && !loading" class="col-span-3 flex flex-col items-center justify-center py-12 text-gray-500">
|
|
||||||
<Icon name="material-symbols:category-outline" class="w-16 h-16 mb-4 text-gray-300" />
|
|
||||||
<p class="text-base font-medium">No processes found</p>
|
|
||||||
<p class="text-sm mt-1">Try selecting a different category or search term</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
title: "Start New Case",
|
title: "Start New Case",
|
||||||
layout: "default",
|
layout: "default",
|
||||||
@ -111,65 +91,61 @@ definePageMeta({
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Data loading states
|
// Mock data for processes
|
||||||
const loading = ref(true);
|
const processes = ref([
|
||||||
const error = ref(null);
|
{
|
||||||
const processes = ref([]);
|
id: 1,
|
||||||
|
name: "Purchase Order Approval",
|
||||||
|
description:
|
||||||
|
"Process for approving purchase orders submitted by departments.",
|
||||||
|
category: "Finance",
|
||||||
|
duration: "3 days",
|
||||||
|
steps: 10,
|
||||||
|
color: "blue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "Leave Request",
|
||||||
|
description:
|
||||||
|
"Process for submitting and approving employee leave requests.",
|
||||||
|
category: "HR",
|
||||||
|
duration: "1 day",
|
||||||
|
steps: 5,
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "Budget Request",
|
||||||
|
description:
|
||||||
|
"Process for requesting and approving department budget allocations.",
|
||||||
|
category: "Finance",
|
||||||
|
duration: "7 days",
|
||||||
|
steps: 12,
|
||||||
|
color: "purple",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "IT Service Request",
|
||||||
|
description:
|
||||||
|
"Process for submitting and handling IT service and support requests.",
|
||||||
|
category: "Operations",
|
||||||
|
duration: "2 days",
|
||||||
|
steps: 7,
|
||||||
|
color: "yellow",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Search and filter
|
// Search and filter
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const selectedCategory = ref("");
|
const selectedCategory = ref("");
|
||||||
const categoryOptions = ref([]);
|
|
||||||
|
|
||||||
// Fetch processes from database
|
// Categories for filter
|
||||||
const fetchProcesses = async () => {
|
const categories = [
|
||||||
try {
|
{ value: "", label: "All Categories" },
|
||||||
loading.value = true;
|
{ value: "HR", label: "Human Resources" },
|
||||||
error.value = null;
|
{ value: "Finance", label: "Finance" },
|
||||||
|
{ value: "Operations", label: "Operations" },
|
||||||
// Only fetch published processes that are not templates
|
];
|
||||||
const response = await $fetch('/api/process', {
|
|
||||||
params: {
|
|
||||||
status: 'published',
|
|
||||||
isTemplate: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
processes.value = response.data.processes.map(process => ({
|
|
||||||
id: process.processUUID,
|
|
||||||
name: process.processName,
|
|
||||||
description: process.processDescription,
|
|
||||||
category: process.processCategory || 'Uncategorized',
|
|
||||||
status: process.processStatus,
|
|
||||||
createdAt: process.processCreatedDate,
|
|
||||||
updatedAt: process.processModifiedDate
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Extract unique categories from processes
|
|
||||||
const uniqueCategories = [...new Set(processes.value.map(p => p.category))].sort();
|
|
||||||
categoryOptions.value = [
|
|
||||||
{ value: "", label: "All Categories" },
|
|
||||||
...uniqueCategories.map(category => ({
|
|
||||||
value: category,
|
|
||||||
label: category
|
|
||||||
}))
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
error.value = response.error || 'Failed to fetch processes';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error fetching processes:', err);
|
|
||||||
error.value = 'An error occurred while fetching processes';
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Computed categories for filter dropdown
|
|
||||||
const categories = computed(() => {
|
|
||||||
return categoryOptions.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtered processes
|
// Filtered processes
|
||||||
const filteredProcesses = computed(() => {
|
const filteredProcesses = computed(() => {
|
||||||
@ -189,62 +165,9 @@ const filteredProcesses = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get color based on category
|
|
||||||
const getProcessColor = (category) => {
|
|
||||||
const colorMap = {
|
|
||||||
'HR': 'green',
|
|
||||||
'Finance': 'blue',
|
|
||||||
'Operations': 'yellow',
|
|
||||||
'IT': 'purple',
|
|
||||||
'Legal': 'red',
|
|
||||||
'Marketing': 'indigo',
|
|
||||||
'Sales': 'orange'
|
|
||||||
};
|
|
||||||
|
|
||||||
return colorMap[category] || 'gray';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Format date
|
|
||||||
const formatDate = (dateString) => {
|
|
||||||
if (!dateString) return 'N/A';
|
|
||||||
const date = new Date(dateString);
|
|
||||||
return date.toLocaleDateString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Start a process
|
// Start a process
|
||||||
const startProcess = async (processId) => {
|
const startProcess = (processId) => {
|
||||||
try {
|
// Logic to start a process would go here
|
||||||
loading.value = true;
|
console.log("Starting process:", processId);
|
||||||
error.value = null;
|
|
||||||
|
|
||||||
// Start the process
|
|
||||||
const response = await $fetch(`/api/process/${processId}/start`, {
|
|
||||||
method: 'POST'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
// Navigate to the first task
|
|
||||||
if (response.data.case) {
|
|
||||||
router.push(`/execution/form/${response.data.case.id}`);
|
|
||||||
} else {
|
|
||||||
error.value = 'No tasks found in the process';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Process start failed:', response);
|
|
||||||
error.value = response.error || 'Failed to start process. Please try again.';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error starting process:', err);
|
|
||||||
error.value = err.message || 'An error occurred while starting the process. Please try again.';
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch processes on component mount
|
|
||||||
onMounted(() => {
|
|
||||||
fetchProcesses();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
export default defineNuxtPlugin((nuxtApp) => {
|
|
||||||
nuxtApp.hook('app:created', () => {
|
|
||||||
// Check if we're on the client side
|
|
||||||
if (process.client) {
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Add global navigation guard
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
// Check if the route path contains literal square brackets
|
|
||||||
// This indicates a user is trying to navigate to a route with [param] directly
|
|
||||||
if (to.fullPath.includes('/[') || to.fullPath.includes(']')) {
|
|
||||||
console.warn('Invalid route detected with literal brackets:', to.fullPath);
|
|
||||||
|
|
||||||
// Extract the route pattern without the brackets
|
|
||||||
const baseRoute = to.fullPath.split('/').slice(0, -1).join('/');
|
|
||||||
|
|
||||||
// Redirect to a more appropriate page
|
|
||||||
return next({
|
|
||||||
path: baseRoute || '/',
|
|
||||||
query: { error: 'invalid_route_format' }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
@ -110,24 +110,6 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/userrole"
|
"$ref": "#/definitions/userrole"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"startedCases": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/caseInstance"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"assignedTasks": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/task"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"caseTimelineEntries": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/caseTimeline"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -283,7 +265,7 @@
|
|||||||
"$ref": "#/definitions/formHistory"
|
"$ref": "#/definitions/formHistory"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tasks": {
|
"task": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/task"
|
"$ref": "#/definitions/task"
|
||||||
@ -512,12 +494,6 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/processHistory"
|
"$ref": "#/definitions/processHistory"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"cases": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/caseInstance"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -701,10 +677,7 @@
|
|||||||
],
|
],
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
},
|
},
|
||||||
"process": {
|
"user": {
|
||||||
"$ref": "#/definitions/process"
|
|
||||||
},
|
|
||||||
"startedBy": {
|
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/user"
|
"$ref": "#/definitions/user"
|
||||||
@ -714,17 +687,54 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tasks": {
|
"process": {
|
||||||
"type": "array",
|
"$ref": "#/definitions/process"
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/task"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"timeline": {
|
"caseTimeline": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/caseTimeline"
|
"$ref": "#/definitions/caseTimeline"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/task"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"caseTimeline": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"timelineID": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"timelineType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timelineDescription": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timelineDate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"caseInstance": {
|
||||||
|
"$ref": "#/definitions/caseInstance"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -775,10 +785,10 @@
|
|||||||
],
|
],
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
},
|
},
|
||||||
"case": {
|
"caseInstance": {
|
||||||
"$ref": "#/definitions/caseInstance"
|
"$ref": "#/definitions/caseInstance"
|
||||||
},
|
},
|
||||||
"assignedTo": {
|
"user": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/user"
|
"$ref": "#/definitions/user"
|
||||||
@ -799,40 +809,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"caseTimeline": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"timelineID": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"timelineType": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"timelineDescription": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"timelineDate": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
},
|
|
||||||
"case": {
|
|
||||||
"$ref": "#/definitions/caseInstance"
|
|
||||||
},
|
|
||||||
"createdBy": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/user"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -861,11 +837,11 @@
|
|||||||
"caseInstance": {
|
"caseInstance": {
|
||||||
"$ref": "#/definitions/caseInstance"
|
"$ref": "#/definitions/caseInstance"
|
||||||
},
|
},
|
||||||
"task": {
|
|
||||||
"$ref": "#/definitions/task"
|
|
||||||
},
|
|
||||||
"caseTimeline": {
|
"caseTimeline": {
|
||||||
"$ref": "#/definitions/caseTimeline"
|
"$ref": "#/definitions/caseTimeline"
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"$ref": "#/definitions/task"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
72
prisma/migrations/20230808013656_initialize/migration.sql
Normal file
72
prisma/migrations/20230808013656_initialize/migration.sql
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `audit` (
|
||||||
|
`auditID` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`auditIP` VARCHAR(255) NULL,
|
||||||
|
`auditURL` VARCHAR(255) NULL,
|
||||||
|
`auditURLMethod` VARCHAR(255) NULL,
|
||||||
|
`auditURLPayload` VARCHAR(255) NULL,
|
||||||
|
`auditCreatedDate` DATETIME(0) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`auditID`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `user` (
|
||||||
|
`userID` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`userSecretKey` VARCHAR(255) NULL,
|
||||||
|
`userUsername` VARCHAR(255) NULL,
|
||||||
|
`userPassword` VARCHAR(255) NULL,
|
||||||
|
`userFullName` VARCHAR(255) NULL,
|
||||||
|
`userEmail` VARCHAR(255) NULL,
|
||||||
|
`userPhone` VARCHAR(255) NULL,
|
||||||
|
`userStatus` VARCHAR(255) NULL,
|
||||||
|
`userCreatedDate` DATETIME(0) NULL,
|
||||||
|
`userModifiedDate` DATETIME(0) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`userID`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `role` (
|
||||||
|
`roleID` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`roleName` VARCHAR(255) NULL,
|
||||||
|
`roleDescription` VARCHAR(255) NULL,
|
||||||
|
`roleStatus` VARCHAR(255) NULL,
|
||||||
|
`roleCreatedDate` DATETIME(0) NULL,
|
||||||
|
`roleModifiedDate` DATETIME(0) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`roleID`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `lookup` (
|
||||||
|
`lookupID` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`lookupOrder` INTEGER NULL,
|
||||||
|
`lookupTitle` VARCHAR(255) NULL,
|
||||||
|
`lookupRefCode` VARCHAR(255) NULL,
|
||||||
|
`lookupValue` VARCHAR(255) NULL,
|
||||||
|
`lookupType` VARCHAR(255) NULL,
|
||||||
|
`lookupStatus` VARCHAR(255) NULL,
|
||||||
|
`lookupCreatedDate` DATETIME(0) NULL,
|
||||||
|
`lookupModifiedDate` DATETIME(0) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`lookupID`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `userrole` (
|
||||||
|
`userRoleID` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`userRoleUserID` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`userRoleRoleID` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`userRoleCreatedDate` DATETIME(0) NOT NULL,
|
||||||
|
|
||||||
|
INDEX `FK_userrole_role`(`userRoleRoleID`),
|
||||||
|
INDEX `FK_userrole_user`(`userRoleUserID`),
|
||||||
|
PRIMARY KEY (`userRoleID`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `userrole` ADD CONSTRAINT `FK_userrole_role` FOREIGN KEY (`userRoleRoleID`) REFERENCES `role`(`roleID`) ON DELETE NO ACTION ON UPDATE NO ACTION;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `userrole` ADD CONSTRAINT `FK_userrole_user` FOREIGN KEY (`userRoleUserID`) REFERENCES `user`(`userID`) ON DELETE NO ACTION ON UPDATE NO ACTION;
|
@ -1,56 +0,0 @@
|
|||||||
-- CreateTable
|
|
||||||
CREATE TABLE `caseInstance` (
|
|
||||||
`caseID` INTEGER NOT NULL AUTO_INCREMENT,
|
|
||||||
`caseUUID` VARCHAR(36) NOT NULL,
|
|
||||||
`processID` INTEGER NOT NULL,
|
|
||||||
`caseName` VARCHAR(255) NOT NULL,
|
|
||||||
`caseStatus` VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
||||||
`caseStartedBy` INTEGER NULL,
|
|
||||||
`caseVariables` JSON NULL,
|
|
||||||
`caseSettings` JSON NULL,
|
|
||||||
`caseDefinition` JSON NULL,
|
|
||||||
`caseCreatedDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`caseModifiedDate` DATETIME(0) NULL,
|
|
||||||
`caseCompletedDate` DATETIME(0) NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `caseInstance_caseUUID_key`(`caseUUID`),
|
|
||||||
INDEX `FK_case_process`(`processID`),
|
|
||||||
INDEX `FK_case_startedBy`(`caseStartedBy`),
|
|
||||||
INDEX `IDX_case_status`(`caseStatus`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `task` (
|
|
||||||
`taskID` INTEGER NOT NULL AUTO_INCREMENT,
|
|
||||||
`taskUUID` VARCHAR(36) NOT NULL,
|
|
||||||
`caseID` INTEGER NOT NULL,
|
|
||||||
`taskName` VARCHAR(255) NOT NULL,
|
|
||||||
`taskType` VARCHAR(50) NOT NULL,
|
|
||||||
`taskStatus` VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
||||||
`taskAssignedTo` INTEGER NULL,
|
|
||||||
`taskFormID` INTEGER NULL,
|
|
||||||
`taskData` JSON NULL,
|
|
||||||
`taskCreatedDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`taskModifiedDate` DATETIME(0) NULL,
|
|
||||||
`taskCompletedDate` DATETIME(0) NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `task_taskUUID_key`(`taskUUID`),
|
|
||||||
INDEX `FK_task_case`(`caseID`),
|
|
||||||
INDEX `FK_task_assignedTo`(`taskAssignedTo`),
|
|
||||||
INDEX `FK_task_form`(`taskFormID`),
|
|
||||||
INDEX `IDX_task_status`(`taskStatus`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `caseTimeline` (
|
|
||||||
`timelineID` INTEGER NOT NULL AUTO_INCREMENT,
|
|
||||||
`caseID` INTEGER NOT NULL,
|
|
||||||
`timelineType` VARCHAR(50) NOT NULL,
|
|
||||||
`timelineDescription` TEXT NULL,
|
|
||||||
`timelineDate` DATETIME(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
|
||||||
`timelineCreatedBy` INTEGER NULL,
|
|
||||||
|
|
||||||
INDEX `FK_caseTimeline_case`(`caseID`),
|
|
||||||
INDEX `FK_caseTimeline_createdBy`(`timelineCreatedBy`),
|
|
||||||
INDEX `IDX_caseTimeline_date`(`timelineDate`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
@ -1,4 +0,0 @@
|
|||||||
# This is an empty migration.
|
|
||||||
|
|
||||||
migration_name = "20240321000001_add_case_instance_and_task_tables_v2"
|
|
||||||
migration_hash = "20240321000001_add_case_instance_and_task_tables_v2"
|
|
@ -31,9 +31,6 @@ model user {
|
|||||||
processHistoryEntries processHistory[]
|
processHistoryEntries processHistory[]
|
||||||
task task[]
|
task task[]
|
||||||
userrole userrole[]
|
userrole userrole[]
|
||||||
startedCases caseInstance[] @relation("CaseStartedBy")
|
|
||||||
assignedTasks task[] @relation("TaskAssignedTo")
|
|
||||||
caseTimelineEntries caseTimeline[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model role {
|
model role {
|
||||||
@ -74,7 +71,7 @@ model form {
|
|||||||
scriptMode String? @default("safe") @db.VarChar(20)
|
scriptMode String? @default("safe") @db.VarChar(20)
|
||||||
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
|
creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID])
|
||||||
history formHistory[] @relation("FormHistoryEntries")
|
history formHistory[] @relation("FormHistoryEntries")
|
||||||
tasks task[]
|
task task[]
|
||||||
|
|
||||||
@@index([formCreatedBy], map: "FK_form_creator")
|
@@index([formCreatedBy], map: "FK_form_creator")
|
||||||
}
|
}
|
||||||
@ -127,7 +124,6 @@ model process {
|
|||||||
caseInstance caseInstance[]
|
caseInstance caseInstance[]
|
||||||
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
|
creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID])
|
||||||
history processHistory[] @relation("ProcessHistoryEntries")
|
history processHistory[] @relation("ProcessHistoryEntries")
|
||||||
cases caseInstance[]
|
|
||||||
|
|
||||||
@@index([processCreatedBy], map: "FK_process_creator")
|
@@index([processCreatedBy], map: "FK_process_creator")
|
||||||
@@index([processStatus], map: "IDX_process_status")
|
@@index([processStatus], map: "IDX_process_status")
|
||||||
@ -165,62 +161,62 @@ model processHistory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model caseInstance {
|
model caseInstance {
|
||||||
caseID Int @id @default(autoincrement())
|
caseID Int @id @default(autoincrement())
|
||||||
caseUUID String @unique @db.VarChar(36)
|
caseUUID String @unique @db.VarChar(36)
|
||||||
processID Int
|
processID Int
|
||||||
caseName String @db.VarChar(255)
|
caseName String @db.VarChar(255)
|
||||||
caseStatus String @default("active") @db.VarChar(50)
|
caseStatus String @default("active") @db.VarChar(50)
|
||||||
caseStartedBy Int?
|
caseStartedBy Int?
|
||||||
caseVariables Json?
|
caseVariables Json?
|
||||||
caseSettings Json?
|
caseSettings Json?
|
||||||
caseDefinition Json?
|
caseDefinition Json?
|
||||||
caseCreatedDate DateTime @default(now()) @db.DateTime(0)
|
caseCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||||
caseModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
caseModifiedDate DateTime? @db.DateTime(0)
|
||||||
caseCompletedDate DateTime? @db.DateTime(0)
|
caseCompletedDate DateTime? @db.DateTime(0)
|
||||||
process process @relation(fields: [processID], references: [processID])
|
user user? @relation(fields: [caseStartedBy], references: [userID])
|
||||||
startedBy user? @relation("CaseStartedBy", fields: [caseStartedBy], references: [userID])
|
process process @relation(fields: [processID], references: [processID])
|
||||||
tasks task[]
|
caseTimeline caseTimeline[]
|
||||||
timeline caseTimeline[]
|
task task[]
|
||||||
|
|
||||||
@@index([processID], map: "FK_case_process")
|
@@index([processID], map: "FK_case_process")
|
||||||
@@index([caseStartedBy], map: "FK_case_startedBy")
|
@@index([caseStartedBy], map: "FK_case_startedBy")
|
||||||
@@index([caseStatus], map: "IDX_case_status")
|
@@index([caseStatus], map: "IDX_case_status")
|
||||||
}
|
}
|
||||||
|
|
||||||
model task {
|
|
||||||
taskID Int @id @default(autoincrement())
|
|
||||||
taskUUID String @unique @db.VarChar(36)
|
|
||||||
caseID Int
|
|
||||||
taskName String @db.VarChar(255)
|
|
||||||
taskType String @db.VarChar(50)
|
|
||||||
taskStatus String @default("pending") @db.VarChar(50)
|
|
||||||
taskAssignedTo Int?
|
|
||||||
taskFormID Int?
|
|
||||||
taskData Json?
|
|
||||||
taskCreatedDate DateTime @default(now()) @db.DateTime(0)
|
|
||||||
taskModifiedDate DateTime? @updatedAt @db.DateTime(0)
|
|
||||||
taskCompletedDate DateTime? @db.DateTime(0)
|
|
||||||
case caseInstance @relation(fields: [caseID], references: [caseID])
|
|
||||||
assignedTo user? @relation("TaskAssignedTo", fields: [taskAssignedTo], references: [userID])
|
|
||||||
form form? @relation(fields: [taskFormID], references: [formID])
|
|
||||||
|
|
||||||
@@index([caseID], map: "FK_task_case")
|
|
||||||
@@index([taskAssignedTo], map: "FK_task_assignedTo")
|
|
||||||
@@index([taskFormID], map: "FK_task_form")
|
|
||||||
@@index([taskStatus], map: "IDX_task_status")
|
|
||||||
}
|
|
||||||
|
|
||||||
model caseTimeline {
|
model caseTimeline {
|
||||||
timelineID Int @id @default(autoincrement())
|
timelineID Int @id @default(autoincrement())
|
||||||
caseID Int
|
caseID Int
|
||||||
timelineType String @db.VarChar(50)
|
timelineType String @db.VarChar(50)
|
||||||
timelineDescription String? @db.Text
|
timelineDescription String? @db.Text
|
||||||
timelineDate DateTime @default(now()) @db.DateTime(0)
|
timelineDate DateTime @default(now()) @db.DateTime(0)
|
||||||
timelineCreatedBy Int?
|
timelineCreatedBy Int?
|
||||||
case caseInstance @relation(fields: [caseID], references: [caseID])
|
caseInstance caseInstance @relation(fields: [caseID], references: [caseID])
|
||||||
createdBy user? @relation(fields: [timelineCreatedBy], references: [userID])
|
user user? @relation(fields: [timelineCreatedBy], references: [userID])
|
||||||
|
|
||||||
@@index([caseID], map: "FK_caseTimeline_case")
|
@@index([caseID], map: "FK_caseTimeline_case")
|
||||||
@@index([timelineCreatedBy], map: "FK_caseTimeline_createdBy")
|
@@index([timelineCreatedBy], map: "FK_caseTimeline_createdBy")
|
||||||
@@index([timelineDate], map: "IDX_caseTimeline_date")
|
@@index([timelineDate], map: "IDX_caseTimeline_date")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model task {
|
||||||
|
taskID Int @id @default(autoincrement())
|
||||||
|
taskUUID String @unique @db.VarChar(36)
|
||||||
|
caseID Int
|
||||||
|
taskName String @db.VarChar(255)
|
||||||
|
taskType String @db.VarChar(50)
|
||||||
|
taskStatus String @default("pending") @db.VarChar(50)
|
||||||
|
taskAssignedTo Int?
|
||||||
|
taskFormID Int?
|
||||||
|
taskData Json?
|
||||||
|
taskCreatedDate DateTime @default(now()) @db.DateTime(0)
|
||||||
|
taskModifiedDate DateTime? @db.DateTime(0)
|
||||||
|
taskCompletedDate DateTime? @db.DateTime(0)
|
||||||
|
caseInstance caseInstance @relation(fields: [caseID], references: [caseID])
|
||||||
|
user user? @relation(fields: [taskAssignedTo], references: [userID])
|
||||||
|
form form? @relation(fields: [taskFormID], references: [formID])
|
||||||
|
|
||||||
|
@@index([taskAssignedTo], map: "FK_task_assignedTo")
|
||||||
|
@@index([caseID], map: "FK_task_case")
|
||||||
|
@@index([taskFormID], map: "FK_task_form")
|
||||||
|
@@index([taskStatus], map: "IDX_task_status")
|
||||||
|
}
|
||||||
|
@ -1,184 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
try {
|
|
||||||
// Get the case ID from the route parameter
|
|
||||||
const caseId = getRouterParam(event, 'id');
|
|
||||||
|
|
||||||
if (!caseId) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Case ID is required'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the ID is a UUID or numeric ID
|
|
||||||
const isUUID = caseId.length === 36 && caseId.includes('-');
|
|
||||||
|
|
||||||
// Find the case instance by UUID or ID
|
|
||||||
const caseInstance = await prisma.caseInstance.findFirst({
|
|
||||||
where: isUUID
|
|
||||||
? { caseUUID: caseId }
|
|
||||||
: { caseID: parseInt(caseId) },
|
|
||||||
include: {
|
|
||||||
process: {
|
|
||||||
select: {
|
|
||||||
processID: true,
|
|
||||||
processUUID: true,
|
|
||||||
processName: true,
|
|
||||||
processDescription: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
startedBy: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tasks: {
|
|
||||||
include: {
|
|
||||||
form: {
|
|
||||||
select: {
|
|
||||||
formID: true,
|
|
||||||
formUUID: true,
|
|
||||||
formName: true,
|
|
||||||
formDescription: true,
|
|
||||||
formComponents: true,
|
|
||||||
formStatus: true,
|
|
||||||
customCSS: true,
|
|
||||||
customScript: true,
|
|
||||||
formEvents: true,
|
|
||||||
scriptMode: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assignedTo: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timeline: {
|
|
||||||
orderBy: {
|
|
||||||
timelineDate: 'desc'
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
createdBy: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!caseInstance) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Case instance not found'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract forms from tasks and remove duplicates
|
|
||||||
const forms = [];
|
|
||||||
const formIds = new Set();
|
|
||||||
|
|
||||||
for (const task of caseInstance.tasks) {
|
|
||||||
if (task.form) {
|
|
||||||
// Make sure formComponents is properly structured
|
|
||||||
let formComponents = [];
|
|
||||||
try {
|
|
||||||
if (task.form.formComponents) {
|
|
||||||
// Check if formComponents is already an array or needs to be extracted from a structure
|
|
||||||
if (Array.isArray(task.form.formComponents)) {
|
|
||||||
formComponents = task.form.formComponents;
|
|
||||||
} else if (task.form.formComponents.components && Array.isArray(task.form.formComponents.components)) {
|
|
||||||
formComponents = task.form.formComponents.components;
|
|
||||||
} else {
|
|
||||||
// Try to parse if it's a stringified JSON
|
|
||||||
const parsedComponents = typeof task.form.formComponents === 'string'
|
|
||||||
? JSON.parse(task.form.formComponents)
|
|
||||||
: task.form.formComponents;
|
|
||||||
|
|
||||||
if (Array.isArray(parsedComponents)) {
|
|
||||||
formComponents = parsedComponents;
|
|
||||||
} else if (parsedComponents.components && Array.isArray(parsedComponents.components)) {
|
|
||||||
formComponents = parsedComponents.components;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error parsing form components:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract form data from taskData if it exists
|
|
||||||
let formData = null;
|
|
||||||
if (task.taskData && task.taskData.formData) {
|
|
||||||
formData = task.taskData.formData;
|
|
||||||
}
|
|
||||||
|
|
||||||
forms.push({
|
|
||||||
...task.form,
|
|
||||||
formComponents: formComponents,
|
|
||||||
taskId: task.taskID,
|
|
||||||
taskUUID: task.taskUUID,
|
|
||||||
taskStatus: task.taskStatus,
|
|
||||||
taskName: task.taskName,
|
|
||||||
taskData: task.taskData,
|
|
||||||
formData: formData,
|
|
||||||
submittedAt: task.taskData?.submittedAt || null,
|
|
||||||
completedDate: task.taskCompletedDate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the response
|
|
||||||
const response = {
|
|
||||||
caseInstance: {
|
|
||||||
caseID: caseInstance.caseID,
|
|
||||||
caseUUID: caseInstance.caseUUID,
|
|
||||||
caseName: caseInstance.caseName,
|
|
||||||
caseStatus: caseInstance.caseStatus,
|
|
||||||
caseCreatedDate: caseInstance.caseCreatedDate,
|
|
||||||
caseModifiedDate: caseInstance.caseModifiedDate,
|
|
||||||
caseCompletedDate: caseInstance.caseCompletedDate,
|
|
||||||
caseVariables: caseInstance.caseVariables,
|
|
||||||
process: {
|
|
||||||
processID: caseInstance.process.processID,
|
|
||||||
processUUID: caseInstance.process.processUUID,
|
|
||||||
processName: caseInstance.process.processName,
|
|
||||||
processDescription: caseInstance.process.processDescription
|
|
||||||
},
|
|
||||||
startedBy: caseInstance.startedBy ? {
|
|
||||||
userID: caseInstance.startedBy.userID,
|
|
||||||
userFullName: caseInstance.startedBy.userFullName,
|
|
||||||
userUsername: caseInstance.startedBy.userUsername
|
|
||||||
} : null
|
|
||||||
},
|
|
||||||
forms: forms,
|
|
||||||
timeline: caseInstance.timeline
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
...response
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching case instance and forms:', error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to fetch case instance and forms',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,174 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
try {
|
|
||||||
// Get the process ID from the route parameter
|
|
||||||
const processId = getRouterParam(event, 'id');
|
|
||||||
|
|
||||||
if (!processId) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Process ID is required'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Starting process with ID:', processId);
|
|
||||||
|
|
||||||
// Check if the ID is a UUID or numeric ID
|
|
||||||
const isUUID = processId.length === 36 && processId.includes('-');
|
|
||||||
|
|
||||||
// Find the process
|
|
||||||
console.log('Finding process...');
|
|
||||||
const process = await prisma.process.findFirst({
|
|
||||||
where: isUUID
|
|
||||||
? { processUUID: processId }
|
|
||||||
: { processID: parseInt(processId) },
|
|
||||||
include: {
|
|
||||||
creator: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!process) {
|
|
||||||
console.log('Process not found');
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Process not found'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Process found:', process.processName);
|
|
||||||
|
|
||||||
// Check if process is published
|
|
||||||
if (process.processStatus !== 'published') {
|
|
||||||
console.log('Process is not published:', process.processStatus);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Cannot start an unpublished process'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current user (in a real app, this would come from the authenticated user)
|
|
||||||
const currentUser = {
|
|
||||||
userID: 1, // This would be the actual user ID in a real app
|
|
||||||
userFullName: 'John Doe',
|
|
||||||
userUsername: 'johndoe'
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Creating case instance...');
|
|
||||||
// Create a new case instance
|
|
||||||
const caseInstance = await prisma.caseInstance.create({
|
|
||||||
data: {
|
|
||||||
caseUUID: uuidv4(),
|
|
||||||
processID: process.processID,
|
|
||||||
caseName: `${process.processName} - ${new Date().toLocaleDateString()}`,
|
|
||||||
caseStatus: 'active',
|
|
||||||
caseStartedBy: currentUser.userID,
|
|
||||||
caseVariables: process.processVariables || {},
|
|
||||||
caseSettings: process.processSettings || {},
|
|
||||||
caseDefinition: process.processDefinition || {},
|
|
||||||
caseCreatedDate: new Date(),
|
|
||||||
caseModifiedDate: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Case instance created:', caseInstance.caseUUID);
|
|
||||||
|
|
||||||
// Get the process definition
|
|
||||||
const processDefinition = process.processDefinition || {};
|
|
||||||
const nodes = processDefinition.nodes || [];
|
|
||||||
const edges = processDefinition.edges || [];
|
|
||||||
|
|
||||||
// Find all form nodes
|
|
||||||
const formNodes = nodes.filter(node => node.type === 'form');
|
|
||||||
|
|
||||||
if (formNodes.length === 0) {
|
|
||||||
console.log('No form nodes found in process');
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Process does not contain any forms'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${formNodes.length} form nodes`);
|
|
||||||
|
|
||||||
// Create tasks for all forms
|
|
||||||
const tasks = [];
|
|
||||||
for (const formNode of formNodes) {
|
|
||||||
console.log('Creating task for form:', formNode.data?.label);
|
|
||||||
const task = await prisma.task.create({
|
|
||||||
data: {
|
|
||||||
taskUUID: uuidv4(),
|
|
||||||
caseID: caseInstance.caseID,
|
|
||||||
taskName: formNode.data?.label || 'Complete Form',
|
|
||||||
taskType: 'form',
|
|
||||||
taskStatus: 'pending',
|
|
||||||
taskAssignedTo: currentUser.userID,
|
|
||||||
taskFormID: formNode.data?.formId,
|
|
||||||
taskCreatedDate: new Date(),
|
|
||||||
taskModifiedDate: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tasks.push(task);
|
|
||||||
console.log('Task created:', task.taskUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to case timeline
|
|
||||||
console.log('Adding to case timeline...');
|
|
||||||
await prisma.caseTimeline.create({
|
|
||||||
data: {
|
|
||||||
caseID: caseInstance.caseID,
|
|
||||||
timelineType: 'start',
|
|
||||||
timelineDescription: `Process started by ${currentUser.userFullName}`,
|
|
||||||
timelineDate: new Date(),
|
|
||||||
timelineCreatedBy: currentUser.userID
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Process started successfully');
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
case: {
|
|
||||||
id: caseInstance.caseUUID,
|
|
||||||
name: caseInstance.caseName,
|
|
||||||
status: caseInstance.caseStatus,
|
|
||||||
startedAt: caseInstance.caseCreatedDate
|
|
||||||
},
|
|
||||||
tasks: tasks.map(task => ({
|
|
||||||
id: task.taskUUID,
|
|
||||||
name: task.taskName,
|
|
||||||
type: task.taskType,
|
|
||||||
formId: task.taskFormID,
|
|
||||||
status: task.taskStatus
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error starting process:', error);
|
|
||||||
console.error('Error details:', {
|
|
||||||
name: error.name,
|
|
||||||
message: error.message,
|
|
||||||
stack: error.stack,
|
|
||||||
code: error.code
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to start process',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
// Close the Prisma client connection
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,41 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
try {
|
|
||||||
// Get all published processes that haven't been started
|
|
||||||
const processes = await prisma.process.findMany({
|
|
||||||
where: {
|
|
||||||
status: 'published',
|
|
||||||
cases: {
|
|
||||||
none: {
|
|
||||||
status: 'active'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
description: true,
|
|
||||||
status: true,
|
|
||||||
createdAt: true,
|
|
||||||
updatedAt: true
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
updatedAt: 'desc'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: processes
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching pending processes:', error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to fetch pending processes'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,122 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
// Get the task ID from the route params
|
|
||||||
const taskId = event.context.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Find the task
|
|
||||||
let task;
|
|
||||||
|
|
||||||
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
|
|
||||||
// If it looks like a UUID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskUUID: taskId },
|
|
||||||
include: {
|
|
||||||
case: {
|
|
||||||
select: {
|
|
||||||
caseID: true,
|
|
||||||
caseUUID: true,
|
|
||||||
caseName: true,
|
|
||||||
caseStatus: true,
|
|
||||||
process: {
|
|
||||||
select: {
|
|
||||||
processID: true,
|
|
||||||
processUUID: true,
|
|
||||||
processName: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assignedTo: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
select: {
|
|
||||||
formID: true,
|
|
||||||
formUUID: true,
|
|
||||||
formName: true,
|
|
||||||
formDescription: true,
|
|
||||||
formComponents: true,
|
|
||||||
formStatus: true,
|
|
||||||
customCSS: true,
|
|
||||||
customScript: true,
|
|
||||||
formEvents: true,
|
|
||||||
scriptMode: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!isNaN(parseInt(taskId))) {
|
|
||||||
// If it's a numeric ID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskID: parseInt(taskId) },
|
|
||||||
include: {
|
|
||||||
case: {
|
|
||||||
select: {
|
|
||||||
caseID: true,
|
|
||||||
caseUUID: true,
|
|
||||||
caseName: true,
|
|
||||||
caseStatus: true,
|
|
||||||
process: {
|
|
||||||
select: {
|
|
||||||
processID: true,
|
|
||||||
processUUID: true,
|
|
||||||
processName: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assignedTo: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
select: {
|
|
||||||
formID: true,
|
|
||||||
formUUID: true,
|
|
||||||
formName: true,
|
|
||||||
formDescription: true,
|
|
||||||
formComponents: true,
|
|
||||||
formStatus: true,
|
|
||||||
customCSS: true,
|
|
||||||
customScript: true,
|
|
||||||
formEvents: true,
|
|
||||||
scriptMode: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Task not found'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
task
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error fetching task ${taskId}:`, error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to fetch task',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,72 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
// Get the task ID from the route params
|
|
||||||
const taskId = event.context.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse the request body
|
|
||||||
const body = await readBody(event);
|
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
if (!body.formData) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Form data is required'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the task
|
|
||||||
let task;
|
|
||||||
|
|
||||||
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
|
|
||||||
// If it looks like a UUID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskUUID: taskId }
|
|
||||||
});
|
|
||||||
} else if (!isNaN(parseInt(taskId))) {
|
|
||||||
// If it's a numeric ID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskID: parseInt(taskId) }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Task not found'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the task with the draft form data
|
|
||||||
const updatedTask = await prisma.task.update({
|
|
||||||
where: {
|
|
||||||
taskID: task.taskID
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
taskData: {
|
|
||||||
...task.taskData,
|
|
||||||
formData: body.formData,
|
|
||||||
lastSaved: new Date().toISOString(),
|
|
||||||
isDraft: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
task: updatedTask
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error saving draft for task ${taskId}:`, error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to save draft',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,122 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
// Get the task ID from the route params
|
|
||||||
const taskId = event.context.params.id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse the request body
|
|
||||||
const body = await readBody(event);
|
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
if (!body.formData) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Form data is required'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the task
|
|
||||||
let task;
|
|
||||||
|
|
||||||
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(taskId)) {
|
|
||||||
// If it looks like a UUID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskUUID: taskId },
|
|
||||||
include: {
|
|
||||||
case: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (!isNaN(parseInt(taskId))) {
|
|
||||||
// If it's a numeric ID
|
|
||||||
task = await prisma.task.findUnique({
|
|
||||||
where: { taskID: parseInt(taskId) },
|
|
||||||
include: {
|
|
||||||
case: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Task not found'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the task with the submitted form data
|
|
||||||
const updatedTask = await prisma.task.update({
|
|
||||||
where: {
|
|
||||||
taskID: task.taskID
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
taskStatus: 'completed',
|
|
||||||
taskCompletedDate: new Date(),
|
|
||||||
taskData: {
|
|
||||||
...task.taskData,
|
|
||||||
formData: body.formData,
|
|
||||||
submittedAt: new Date().toISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add an entry to the case timeline
|
|
||||||
await prisma.caseTimeline.create({
|
|
||||||
data: {
|
|
||||||
caseID: task.caseID,
|
|
||||||
timelineType: 'task_completed',
|
|
||||||
timelineDescription: `Task "${task.taskName}" was completed`,
|
|
||||||
timelineCreatedBy: task.taskAssignedTo
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if all tasks for the case are completed
|
|
||||||
const remainingTasks = await prisma.task.count({
|
|
||||||
where: {
|
|
||||||
caseID: task.caseID,
|
|
||||||
taskStatus: {
|
|
||||||
not: 'completed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If all tasks are completed, update the case status
|
|
||||||
if (remainingTasks === 0) {
|
|
||||||
await prisma.caseInstance.update({
|
|
||||||
where: {
|
|
||||||
caseID: task.caseID
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
caseStatus: 'completed',
|
|
||||||
caseCompletedDate: new Date()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add an entry to the case timeline
|
|
||||||
await prisma.caseTimeline.create({
|
|
||||||
data: {
|
|
||||||
caseID: task.caseID,
|
|
||||||
timelineType: 'case_completed',
|
|
||||||
timelineDescription: `Case "${task.case.caseName}" was completed`,
|
|
||||||
timelineCreatedBy: task.taskAssignedTo
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
task: updatedTask
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error submitting form for task ${taskId}:`, error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to submit form',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,96 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
// Initialize Prisma client
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
try {
|
|
||||||
// Get query parameters
|
|
||||||
const query = getQuery(event);
|
|
||||||
const caseId = query.caseId;
|
|
||||||
const status = query.status;
|
|
||||||
const assignedTo = query.assignedTo;
|
|
||||||
const page = parseInt(query.page) || 1;
|
|
||||||
const limit = parseInt(query.limit) || 10;
|
|
||||||
const skip = (page - 1) * limit;
|
|
||||||
|
|
||||||
// Build where clause
|
|
||||||
const where = {};
|
|
||||||
|
|
||||||
if (caseId) {
|
|
||||||
where.caseID = parseInt(caseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status) {
|
|
||||||
where.taskStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assignedTo) {
|
|
||||||
where.taskAssignedTo = parseInt(assignedTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch tasks
|
|
||||||
const tasks = await prisma.task.findMany({
|
|
||||||
where,
|
|
||||||
include: {
|
|
||||||
case: {
|
|
||||||
select: {
|
|
||||||
caseID: true,
|
|
||||||
caseUUID: true,
|
|
||||||
caseName: true,
|
|
||||||
caseStatus: true,
|
|
||||||
process: {
|
|
||||||
select: {
|
|
||||||
processID: true,
|
|
||||||
processUUID: true,
|
|
||||||
processName: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assignedTo: {
|
|
||||||
select: {
|
|
||||||
userID: true,
|
|
||||||
userFullName: true,
|
|
||||||
userUsername: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
select: {
|
|
||||||
formID: true,
|
|
||||||
formUUID: true,
|
|
||||||
formName: true,
|
|
||||||
formDescription: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
taskCreatedDate: 'desc'
|
|
||||||
},
|
|
||||||
skip,
|
|
||||||
take: limit
|
|
||||||
});
|
|
||||||
|
|
||||||
// Count total tasks for pagination
|
|
||||||
const totalTasks = await prisma.task.count({ where });
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
tasks,
|
|
||||||
pagination: {
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
totalItems: totalTasks,
|
|
||||||
totalPages: Math.ceil(totalTasks / limit)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching tasks:', error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to fetch tasks',
|
|
||||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,9 +0,0 @@
|
|||||||
export default defineEventHandler((event) => {
|
|
||||||
const url = event.node.req.url;
|
|
||||||
|
|
||||||
// Check if the URL contains literal [id] which should be a parameter
|
|
||||||
if (url && url.includes('/execution/form/[id]')) {
|
|
||||||
// Redirect to a more appropriate error page or handle differently
|
|
||||||
return sendRedirect(event, '/execution', 302);
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
x
Reference in New Issue
Block a user