corrad-bp/pages/execution/new-case.vue
2025-06-16 00:52:49 +08:00

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>