- Simplified navigation structure by consolidating child routes for Process and Form Designers, improving clarity and accessibility. - Updated Form Builder and Process Builder header styles for a more modern look, including logo and title adjustments. - Enhanced button styles and layout in the Form Builder and Process Management pages for better user interaction. - Introduced new dashboard metrics and recent activity sections in Process Management, providing users with quick insights into their processes. - Improved overall responsiveness and visual consistency across the application.
349 lines
9.0 KiB
JavaScript
349 lines
9.0 KiB
JavaScript
import { PrismaClient } from '@prisma/client';
|
|
|
|
// Initialize Prisma client
|
|
const prisma = new PrismaClient();
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
// Get query parameters for filtering (optional)
|
|
const query = getQuery(event);
|
|
const {
|
|
startDate,
|
|
endDate,
|
|
category,
|
|
status
|
|
} = query;
|
|
|
|
// Build date filter
|
|
const dateFilter = {};
|
|
if (startDate && endDate) {
|
|
dateFilter.processCreatedDate = {
|
|
gte: new Date(startDate),
|
|
lte: new Date(endDate)
|
|
};
|
|
}
|
|
|
|
// Build process filter
|
|
const processFilter = {
|
|
...dateFilter
|
|
};
|
|
|
|
if (category) {
|
|
processFilter.processCategory = category;
|
|
}
|
|
|
|
if (status) {
|
|
processFilter.processStatus = status;
|
|
}
|
|
|
|
// Get process statistics
|
|
const [
|
|
totalProcesses,
|
|
publishedProcesses,
|
|
draftProcesses,
|
|
archivedProcesses,
|
|
deletedProcesses,
|
|
processCategories
|
|
] = await Promise.all([
|
|
// Total processes
|
|
prisma.process.count({
|
|
where: processFilter
|
|
}),
|
|
|
|
// Published processes
|
|
prisma.process.count({
|
|
where: {
|
|
...processFilter,
|
|
processStatus: 'published'
|
|
}
|
|
}),
|
|
|
|
// Draft processes
|
|
prisma.process.count({
|
|
where: {
|
|
...processFilter,
|
|
processStatus: 'draft'
|
|
}
|
|
}),
|
|
|
|
// Archived processes
|
|
prisma.process.count({
|
|
where: {
|
|
...processFilter,
|
|
processStatus: 'archived'
|
|
}
|
|
}),
|
|
|
|
// Deleted processes
|
|
prisma.process.count({
|
|
where: {
|
|
...processFilter,
|
|
processStatus: 'deleted'
|
|
}
|
|
}),
|
|
|
|
// Process categories distribution
|
|
prisma.process.groupBy({
|
|
by: ['processCategory'],
|
|
where: processFilter,
|
|
_count: {
|
|
processCategory: true
|
|
}
|
|
})
|
|
]);
|
|
|
|
// Get case instance statistics
|
|
const [
|
|
totalCases,
|
|
activeCases,
|
|
completedCases,
|
|
casesByStatus
|
|
] = await Promise.all([
|
|
// Total case instances
|
|
prisma.caseInstance.count(),
|
|
|
|
// Active cases
|
|
prisma.caseInstance.count({
|
|
where: {
|
|
caseStatus: 'active'
|
|
}
|
|
}),
|
|
|
|
// Completed cases
|
|
prisma.caseInstance.count({
|
|
where: {
|
|
caseStatus: 'completed'
|
|
}
|
|
}),
|
|
|
|
// Cases by status
|
|
prisma.caseInstance.groupBy({
|
|
by: ['caseStatus'],
|
|
_count: {
|
|
caseStatus: true
|
|
}
|
|
})
|
|
]);
|
|
|
|
// Get task statistics
|
|
const [
|
|
totalTasks,
|
|
pendingTasks,
|
|
completedTasks,
|
|
overdueTasks
|
|
] = await Promise.all([
|
|
// Total tasks
|
|
prisma.task.count(),
|
|
|
|
// Pending tasks
|
|
prisma.task.count({
|
|
where: {
|
|
taskStatus: 'pending'
|
|
}
|
|
}),
|
|
|
|
// Completed tasks
|
|
prisma.task.count({
|
|
where: {
|
|
taskStatus: 'completed'
|
|
}
|
|
}),
|
|
|
|
// Overdue tasks (pending tasks created more than 3 days ago)
|
|
prisma.task.count({
|
|
where: {
|
|
taskStatus: 'pending',
|
|
taskCreatedDate: {
|
|
lt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000) // 3 days ago
|
|
}
|
|
}
|
|
})
|
|
]);
|
|
|
|
// Calculate average completion time for completed cases
|
|
const completedCasesWithTime = await prisma.caseInstance.findMany({
|
|
where: {
|
|
caseStatus: 'completed',
|
|
caseCompletedDate: { not: null }
|
|
},
|
|
select: {
|
|
caseCreatedDate: true,
|
|
caseCompletedDate: true
|
|
}
|
|
});
|
|
|
|
let averageCompletionTime = 0;
|
|
if (completedCasesWithTime.length > 0) {
|
|
const totalCompletionTime = completedCasesWithTime.reduce((sum, case_) => {
|
|
const diffMs = new Date(case_.caseCompletedDate) - new Date(case_.caseCreatedDate);
|
|
const diffHours = diffMs / (1000 * 60 * 60);
|
|
return sum + diffHours;
|
|
}, 0);
|
|
|
|
averageCompletionTime = Math.round((totalCompletionTime / completedCasesWithTime.length) * 10) / 10;
|
|
}
|
|
|
|
// Calculate success rate (completed cases / total cases * 100)
|
|
const successRate = totalCases > 0 ? Math.round((completedCases / totalCases) * 1000) / 10 : 0;
|
|
|
|
// Get recent activity (latest case instances and process updates)
|
|
const recentCases = await prisma.caseInstance.findMany({
|
|
take: 5,
|
|
orderBy: {
|
|
caseCreatedDate: 'desc'
|
|
},
|
|
include: {
|
|
process: {
|
|
select: {
|
|
processName: true
|
|
}
|
|
},
|
|
startedBy: {
|
|
select: {
|
|
userFullName: true,
|
|
userUsername: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const recentProcessUpdates = await prisma.process.findMany({
|
|
take: 5,
|
|
orderBy: {
|
|
processModifiedDate: 'desc'
|
|
},
|
|
where: {
|
|
processModifiedDate: { not: null }
|
|
},
|
|
include: {
|
|
creator: {
|
|
select: {
|
|
userFullName: true,
|
|
userUsername: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Format recent activity
|
|
const recentActivity = [];
|
|
|
|
// Add recent cases
|
|
recentCases.forEach(case_ => {
|
|
recentActivity.push({
|
|
id: `case-${case_.caseID}`,
|
|
type: case_.caseStatus === 'completed' ? 'case_completed' : 'case_started',
|
|
message: `${case_.process.processName} case ${case_.caseStatus === 'completed' ? 'completed' : 'started'}`,
|
|
user: case_.startedBy?.userFullName || case_.startedBy?.userUsername || 'Unknown',
|
|
timestamp: case_.caseStatus === 'completed' ? case_.caseCompletedDate : case_.caseCreatedDate
|
|
});
|
|
});
|
|
|
|
// Add recent process updates
|
|
recentProcessUpdates.forEach(process => {
|
|
recentActivity.push({
|
|
id: `process-${process.processID}`,
|
|
type: process.processStatus === 'published' ? 'process_published' : 'process_updated',
|
|
message: `${process.processName} process ${process.processStatus === 'published' ? 'published' : 'updated'}`,
|
|
user: process.creator?.userFullName || process.creator?.userUsername || 'Unknown',
|
|
timestamp: process.processModifiedDate
|
|
});
|
|
});
|
|
|
|
// Sort recent activity by timestamp and take the latest 10
|
|
recentActivity.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
const latestActivity = recentActivity.slice(0, 10);
|
|
|
|
// Get monthly statistics for the last 6 months
|
|
const sixMonthsAgo = new Date();
|
|
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
|
|
|
const monthlyCases = await prisma.caseInstance.groupBy({
|
|
by: ['caseCreatedDate'],
|
|
where: {
|
|
caseCreatedDate: {
|
|
gte: sixMonthsAgo
|
|
}
|
|
},
|
|
_count: {
|
|
caseID: true
|
|
}
|
|
});
|
|
|
|
// Process monthly data
|
|
const monthlyStats = [];
|
|
for (let i = 5; i >= 0; i--) {
|
|
const date = new Date();
|
|
date.setMonth(date.getMonth() - i);
|
|
const monthKey = date.toISOString().substring(0, 7); // YYYY-MM format
|
|
|
|
const monthData = monthlyCases.filter(item =>
|
|
item.caseCreatedDate.toISOString().substring(0, 7) === monthKey
|
|
);
|
|
|
|
monthlyStats.push({
|
|
month: date.toLocaleString('default', { month: 'short' }),
|
|
year: date.getFullYear(),
|
|
cases: monthData.length,
|
|
completed: 0 // Would need additional query to get completed cases for the month
|
|
});
|
|
}
|
|
|
|
// Prepare response
|
|
const summary = {
|
|
overview: {
|
|
totalProcesses,
|
|
publishedProcesses,
|
|
draftProcesses,
|
|
archivedProcesses,
|
|
deletedProcesses,
|
|
totalCases,
|
|
activeCases,
|
|
completedCases,
|
|
totalTasks,
|
|
pendingTasks,
|
|
completedTasks,
|
|
overdueTasks,
|
|
averageCompletionTime,
|
|
successRate
|
|
},
|
|
charts: {
|
|
processDistribution: [
|
|
{ label: 'Published', value: publishedProcesses, color: '#10B981' },
|
|
{ label: 'Draft', value: draftProcesses, color: '#F59E0B' },
|
|
{ label: 'Archived', value: archivedProcesses, color: '#6B7280' },
|
|
{ label: 'Deleted', value: deletedProcesses, color: '#EF4444' }
|
|
],
|
|
caseStatus: casesByStatus.map(item => ({
|
|
label: item.caseStatus,
|
|
value: item._count.caseStatus,
|
|
color: item.caseStatus === 'active' ? '#3B82F6' :
|
|
item.caseStatus === 'completed' ? '#10B981' : '#6B7280'
|
|
})),
|
|
categoryDistribution: processCategories.map(item => ({
|
|
label: item.processCategory || 'Uncategorized',
|
|
value: item._count.processCategory
|
|
})),
|
|
monthlyTrend: monthlyStats
|
|
},
|
|
recentActivity: latestActivity,
|
|
lastUpdated: new Date().toISOString()
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
data: summary
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching dashboard summary:', error);
|
|
|
|
return {
|
|
success: false,
|
|
error: 'Failed to fetch dashboard summary',
|
|
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
|
};
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
}); |