251 lines
7.1 KiB
Vue
251 lines
7.1 KiB
Vue
<template>
|
|
<div>
|
|
<LayoutsBreadcrumb />
|
|
|
|
<!-- Search and Filter -->
|
|
<rs-card>
|
|
<div class="p-5 flex flex-wrap gap-4">
|
|
<div class="w-full md:w-1/2">
|
|
<FormKit
|
|
type="text"
|
|
name="search"
|
|
placeholder="Search processes..."
|
|
prefix-icon="search"
|
|
v-model="searchQuery"
|
|
/>
|
|
</div>
|
|
<div class="w-full md:w-1/3">
|
|
<FormKit
|
|
type="select"
|
|
name="category"
|
|
placeholder="Select category"
|
|
:options="categories"
|
|
v-model="selectedCategory"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</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 -->
|
|
<div v-if="!loading" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<rs-card
|
|
v-for="process in filteredProcesses"
|
|
:key="process.id"
|
|
class="overflow-hidden hover:shadow-md transition-shadow duration-300"
|
|
>
|
|
<div :class="`h-3 bg-${getProcessColor(process.category)}-500`"></div>
|
|
<template #body>
|
|
<div class="p-5">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">{{ process.name }}</h3>
|
|
<rs-badge
|
|
:variant="
|
|
process.category === 'HR'
|
|
? 'info'
|
|
: process.category === 'Finance'
|
|
? 'success'
|
|
: 'warning'
|
|
"
|
|
>
|
|
{{ process.category }}
|
|
</rs-badge>
|
|
</div>
|
|
<p class="text-gray-600 text-sm mb-4">{{ process.description }}</p>
|
|
<div class="text-xs text-gray-500 mb-4">
|
|
<div class="flex items-center mb-1">
|
|
<Icon
|
|
class="text-base mr-1"
|
|
name="material-symbols:schedule"
|
|
></Icon>
|
|
<span>Created: {{ formatDate(process.createdAt) }}</span>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<Icon
|
|
class="text-base mr-1"
|
|
name="material-symbols:sync"
|
|
></Icon>
|
|
<span>Status: {{ process.status }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<rs-button
|
|
variant="primary"
|
|
block
|
|
@click="startProcess(process.id)"
|
|
>
|
|
Start Process
|
|
</rs-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</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>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
|
|
definePageMeta({
|
|
title: "Start New Case",
|
|
layout: "default",
|
|
middleware: ["auth"],
|
|
requiresAuth: true,
|
|
});
|
|
|
|
// Data loading states
|
|
const loading = ref(true);
|
|
const error = ref(null);
|
|
const processes = ref([]);
|
|
|
|
// Search and filter
|
|
const searchQuery = ref("");
|
|
const selectedCategory = ref("");
|
|
const categoryOptions = ref([]);
|
|
|
|
// Fetch processes from database
|
|
const fetchProcesses = async () => {
|
|
try {
|
|
loading.value = true;
|
|
error.value = null;
|
|
|
|
// 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
|
|
const filteredProcesses = computed(() => {
|
|
return processes.value.filter((process) => {
|
|
const matchesSearch =
|
|
searchQuery.value === "" ||
|
|
process.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
process.description
|
|
.toLowerCase()
|
|
.includes(searchQuery.value.toLowerCase());
|
|
|
|
const matchesCategory =
|
|
selectedCategory.value === "" ||
|
|
process.category === selectedCategory.value;
|
|
|
|
return matchesSearch && matchesCategory;
|
|
});
|
|
});
|
|
|
|
// 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
|
|
const startProcess = async (processId) => {
|
|
try {
|
|
loading.value = true;
|
|
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>
|