generated from corrad-software/corrad-af-2024
Added Admin Dashboard, Access Management
This commit is contained in:
parent
8cff207c45
commit
d4880c491e
@ -10,11 +10,55 @@ export default [
|
|||||||
"child": [],
|
"child": [],
|
||||||
"meta": {}
|
"meta": {}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Admin Dashboard",
|
||||||
|
"path": "/dms/admin-dashboard",
|
||||||
|
"icon": "material-symbols:dashboard",
|
||||||
|
"child": [],
|
||||||
|
"meta": {
|
||||||
|
"auth": {
|
||||||
|
"role": ["admin", "superadmin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Access Management",
|
||||||
|
"path": "/dms/access-management",
|
||||||
|
"icon": "ic:baseline-security",
|
||||||
|
"child": [],
|
||||||
|
"meta": {
|
||||||
|
"auth": {
|
||||||
|
"role": ["admin", "superadmin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Role Management",
|
||||||
|
"path": "/dms/role-management",
|
||||||
|
"icon": "mdi:account-key",
|
||||||
|
"child": [],
|
||||||
|
"meta": {
|
||||||
|
"auth": {
|
||||||
|
"role": ["superadmin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Switch Role",
|
||||||
|
"path": "/dms/switch-roles",
|
||||||
|
"icon": "ic:outline-swap-horiz",
|
||||||
|
"child": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"path": "/dms/settings",
|
"path": "/dms/settings",
|
||||||
"icon": "ic:outline-settings",
|
"icon": "ic:outline-settings",
|
||||||
"child": []
|
"child": [],
|
||||||
|
"meta": {
|
||||||
|
"auth": {
|
||||||
|
"role": ["admin", "superadmin"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {}
|
"meta": {}
|
||||||
@ -86,7 +130,7 @@ export default [
|
|||||||
"meta": {
|
"meta": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"role": [
|
"role": [
|
||||||
"Developer"
|
"superadmin"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
541
pages/dms/admin-dashboard.vue
Normal file
541
pages/dms/admin-dashboard.vue
Normal file
@ -0,0 +1,541 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { useDmsStore } from '~/stores/dms';
|
||||||
|
|
||||||
|
// Define page metadata
|
||||||
|
definePageMeta({
|
||||||
|
title: "Admin Dashboard",
|
||||||
|
middleware: ["auth"],
|
||||||
|
requiresAuth: true,
|
||||||
|
breadcrumb: [
|
||||||
|
{ name: "DMS", path: "/dms" },
|
||||||
|
{ name: "Admin Dashboard", path: "/dms/admin-dashboard" }
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const dmsStore = useDmsStore();
|
||||||
|
|
||||||
|
// Component state
|
||||||
|
const isLoading = ref(true);
|
||||||
|
const timeRange = ref('30days');
|
||||||
|
const accessRequests = ref([]);
|
||||||
|
const deptRequests = ref([]);
|
||||||
|
|
||||||
|
// Time range options
|
||||||
|
const timeRangeOptions = [
|
||||||
|
{ id: '7days', label: 'Last 7 Days' },
|
||||||
|
{ id: '30days', label: 'Last 30 Days' },
|
||||||
|
{ id: '90days', label: 'Last 90 Days' },
|
||||||
|
{ id: '365days', label: 'Last Year' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if user is admin
|
||||||
|
const isAdmin = computed(() => {
|
||||||
|
return dmsStore.currentUser.role === 'admin';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate KPIs
|
||||||
|
const kpis = computed(() => {
|
||||||
|
if (!accessRequests.value.length) return null;
|
||||||
|
|
||||||
|
const total = accessRequests.value.length;
|
||||||
|
const pending = accessRequests.value.filter(req => req.status === 'pending').length;
|
||||||
|
const approved = accessRequests.value.filter(req => req.status === 'approved').length;
|
||||||
|
const rejected = accessRequests.value.filter(req => req.status === 'rejected').length;
|
||||||
|
|
||||||
|
// Calculate response times
|
||||||
|
const resolvedRequests = accessRequests.value.filter(
|
||||||
|
req => req.status === 'approved' || req.status === 'rejected'
|
||||||
|
);
|
||||||
|
|
||||||
|
let totalResponseTime = 0;
|
||||||
|
let fastestResponse = Infinity;
|
||||||
|
let slowestResponse = 0;
|
||||||
|
|
||||||
|
resolvedRequests.forEach(req => {
|
||||||
|
const requestDate = new Date(req.requestDate);
|
||||||
|
const responseDate = new Date(req.responseDate);
|
||||||
|
const responseTimeHours = (responseDate - requestDate) / (1000 * 60 * 60);
|
||||||
|
|
||||||
|
totalResponseTime += responseTimeHours;
|
||||||
|
fastestResponse = Math.min(fastestResponse, responseTimeHours);
|
||||||
|
slowestResponse = Math.max(slowestResponse, responseTimeHours);
|
||||||
|
});
|
||||||
|
|
||||||
|
const avgResponseTime = resolvedRequests.length > 0
|
||||||
|
? totalResponseTime / resolvedRequests.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Calculate overdue
|
||||||
|
const overdueRequests = accessRequests.value.filter(req => {
|
||||||
|
if (req.status !== 'pending') return false;
|
||||||
|
|
||||||
|
const deadline = new Date(req.targetResolutionTime);
|
||||||
|
const now = new Date();
|
||||||
|
return now > deadline;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
const overduePercentage = total > 0
|
||||||
|
? (overdueRequests / total) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Calculate SLA compliance
|
||||||
|
const slaCompliance = resolvedRequests.length > 0
|
||||||
|
? (resolvedRequests.filter(req => {
|
||||||
|
const requestDate = new Date(req.requestDate);
|
||||||
|
const responseDate = new Date(req.responseDate);
|
||||||
|
const targetDate = new Date(req.targetResolutionTime);
|
||||||
|
return responseDate <= targetDate;
|
||||||
|
}).length / resolvedRequests.length) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Approval rate
|
||||||
|
const approvalRate = resolvedRequests.length > 0
|
||||||
|
? (approved / resolvedRequests.length) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
pending,
|
||||||
|
approved,
|
||||||
|
rejected,
|
||||||
|
avgResponseTime,
|
||||||
|
fastestResponse: fastestResponse === Infinity ? 0 : fastestResponse,
|
||||||
|
slowestResponse,
|
||||||
|
overdueRequests,
|
||||||
|
overduePercentage,
|
||||||
|
slaCompliance,
|
||||||
|
approvalRate
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Department performance metrics
|
||||||
|
const deptPerformance = computed(() => {
|
||||||
|
if (!deptRequests.value.length) return [];
|
||||||
|
|
||||||
|
// Group by department
|
||||||
|
const departments = {};
|
||||||
|
deptRequests.value.forEach(req => {
|
||||||
|
const dept = req.approverDepartment || 'Unassigned';
|
||||||
|
if (!departments[dept]) {
|
||||||
|
departments[dept] = {
|
||||||
|
name: dept,
|
||||||
|
total: 0,
|
||||||
|
approved: 0,
|
||||||
|
rejected: 0,
|
||||||
|
pending: 0,
|
||||||
|
avgResponseHours: 0,
|
||||||
|
responseCount: 0,
|
||||||
|
totalResponseHours: 0,
|
||||||
|
overdueCount: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const deptData = departments[dept];
|
||||||
|
deptData.total++;
|
||||||
|
|
||||||
|
if (req.status === 'approved') deptData.approved++;
|
||||||
|
if (req.status === 'rejected') deptData.rejected++;
|
||||||
|
if (req.status === 'pending') deptData.pending++;
|
||||||
|
|
||||||
|
// Calculate response time
|
||||||
|
if (req.responseDate) {
|
||||||
|
const requestDate = new Date(req.requestDate);
|
||||||
|
const responseDate = new Date(req.responseDate);
|
||||||
|
const responseTimeHours = (responseDate - requestDate) / (1000 * 60 * 60);
|
||||||
|
deptData.totalResponseHours += responseTimeHours;
|
||||||
|
deptData.responseCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if overdue
|
||||||
|
if (req.status === 'pending') {
|
||||||
|
const deadline = new Date(req.targetResolutionTime);
|
||||||
|
const now = new Date();
|
||||||
|
if (now > deadline) {
|
||||||
|
deptData.overdueCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate averages and format data
|
||||||
|
return Object.values(departments).map(dept => {
|
||||||
|
dept.avgResponseHours = dept.responseCount > 0
|
||||||
|
? dept.totalResponseHours / dept.responseCount
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
dept.overduePercentage = dept.total > 0
|
||||||
|
? (dept.overdueCount / dept.total) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
dept.approvalRate = (dept.approved + dept.rejected) > 0
|
||||||
|
? (dept.approved / (dept.approved + dept.rejected)) * 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return dept;
|
||||||
|
}).sort((a, b) => b.total - a.total);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format helpers
|
||||||
|
const formatDuration = (hours) => {
|
||||||
|
if (hours < 1) {
|
||||||
|
return `${Math.round(hours * 60)} minutes`;
|
||||||
|
} else if (hours < 24) {
|
||||||
|
return `${Math.round(hours)} hours`;
|
||||||
|
} else {
|
||||||
|
return `${Math.round(hours / 24)} days`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPercentage = (value) => {
|
||||||
|
return `${Math.round(value)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load data
|
||||||
|
const loadData = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the store instance
|
||||||
|
const store = useDmsStore();
|
||||||
|
|
||||||
|
// Load access metrics based on selected time range
|
||||||
|
const metrics = await store.getAccessRequestMetrics(timeRange.value);
|
||||||
|
|
||||||
|
// Load all access requests
|
||||||
|
const [requests, departmentReqs] = await Promise.all([
|
||||||
|
store.getAccessRequests(),
|
||||||
|
store.getDepartmentAccessRequests()
|
||||||
|
]);
|
||||||
|
|
||||||
|
accessRequests.value = requests;
|
||||||
|
deptRequests.value = departmentReqs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load dashboard data:', error);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update time range
|
||||||
|
const updateTimeRange = (range) => {
|
||||||
|
timeRange.value = range;
|
||||||
|
loadData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMounted(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="admin-dashboard">
|
||||||
|
<LayoutsBreadcrumb />
|
||||||
|
|
||||||
|
<rs-card class="h-full">
|
||||||
|
<template #body>
|
||||||
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
|
<!-- Admin check -->
|
||||||
|
<div v-if="!isAdmin" class="flex-1 flex items-center justify-center overflow-hidden">
|
||||||
|
<div class="text-center p-6 max-w-md">
|
||||||
|
<div class="text-red-500 text-5xl mb-4">⚠️</div>
|
||||||
|
<h2 class="text-xl font-semibold text-red-600 mb-2">Admin Access Required</h2>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
You need administrator privileges to access this dashboard.
|
||||||
|
</p>
|
||||||
|
<NuxtLink to="/dms/switch-roles" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium inline-block">
|
||||||
|
Switch Role
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin Dashboard -->
|
||||||
|
<template v-else>
|
||||||
|
<!-- Dashboard Header -->
|
||||||
|
<div class="bg-indigo-50 dark:bg-indigo-900/20 px-6 py-4 border-b border-indigo-200 dark:border-indigo-800 flex-shrink-0">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl font-semibold text-indigo-900 dark:text-indigo-100">
|
||||||
|
Access Request Performance Dashboard
|
||||||
|
</h1>
|
||||||
|
<p class="text-indigo-700 dark:text-indigo-300 text-sm">
|
||||||
|
Monitor approval KPIs, response times, and department performance
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time Range Selector -->
|
||||||
|
<div class="flex items-center self-end sm:self-center">
|
||||||
|
<div class="inline-flex rounded-md shadow-sm mr-2">
|
||||||
|
<button
|
||||||
|
v-for="option in timeRangeOptions"
|
||||||
|
:key="option.id"
|
||||||
|
@click="updateTimeRange(option.id)"
|
||||||
|
class="relative inline-flex items-center px-3 py-1.5 text-sm font-medium border transition-colors whitespace-nowrap"
|
||||||
|
:class="[
|
||||||
|
timeRange === option.id
|
||||||
|
? 'bg-indigo-100 border-indigo-300 text-indigo-800 dark:bg-indigo-900/20 dark:border-indigo-700 dark:text-indigo-300 z-10'
|
||||||
|
: 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300',
|
||||||
|
option.id === '7days' ? 'rounded-l-md' : '',
|
||||||
|
option.id === '365days' ? 'rounded-r-md' : ''
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="loadData"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="isLoading" class="flex-1 flex items-center justify-center overflow-hidden">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-indigo-600 mx-auto mb-4"></div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">Loading dashboard data...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard Content -->
|
||||||
|
<div v-else class="flex-1 overflow-auto p-4 md:p-6">
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div class="flex flex-wrap justify-end mb-4 gap-2">
|
||||||
|
<NuxtLink
|
||||||
|
to="/dms"
|
||||||
|
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
Return to DMS
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<NuxtLink
|
||||||
|
to="/dms/switch-roles"
|
||||||
|
class="inline-flex items-center px-3 py-2 border border-indigo-300 dark:border-indigo-600 rounded-md text-sm font-medium text-indigo-700 dark:text-indigo-300 bg-white dark:bg-gray-800 hover:bg-indigo-50 dark:hover:bg-indigo-900/10"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
Switch Role
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- KPI Summary Cards -->
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
|
<!-- Total Requests -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-12 h-12 rounded-lg bg-blue-100 dark:bg-blue-900/20 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ kpis?.total || 0 }}</div>
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">Total Requests</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pending Requests -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-12 h-12 rounded-lg bg-yellow-100 dark:bg-yellow-900/20 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-yellow-600 dark:text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ kpis?.pending || 0 }}</div>
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">Pending Requests</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Avg Response Time -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-12 h-12 rounded-lg bg-green-100 dark:bg-green-900/20 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ formatDuration(kpis?.avgResponseTime || 0) }}</div>
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">Avg Response Time</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SLA Compliance -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-12 h-12 rounded-lg bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ formatPercentage(kpis?.slaCompliance || 0) }}</div>
|
||||||
|
<div class="text-sm text-gray-500 dark:text-gray-400">SLA Compliance</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Performance Metrics -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||||
|
<!-- Response Time Metrics -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h2 class="font-medium text-gray-900 dark:text-gray-100">Response Time Metrics</h2>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div class="p-3 bg-blue-50 dark:bg-blue-900/10 rounded-lg border border-blue-100 dark:border-blue-800">
|
||||||
|
<div class="text-sm text-blue-700 dark:text-blue-300 mb-1">Average</div>
|
||||||
|
<div class="text-xl font-bold text-blue-900 dark:text-blue-100 truncate" title="{{ formatDuration(kpis?.avgResponseTime || 0) }}">{{ formatDuration(kpis?.avgResponseTime || 0) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 bg-green-50 dark:bg-green-900/10 rounded-lg border border-green-100 dark:border-green-800">
|
||||||
|
<div class="text-sm text-green-700 dark:text-green-300 mb-1">Fastest</div>
|
||||||
|
<div class="text-xl font-bold text-green-900 dark:text-green-100 truncate" title="{{ formatDuration(kpis?.fastestResponse || 0) }}">{{ formatDuration(kpis?.fastestResponse || 0) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-100 dark:border-red-800">
|
||||||
|
<div class="text-sm text-red-700 dark:text-red-300 mb-1">Slowest</div>
|
||||||
|
<div class="text-xl font-bold text-red-900 dark:text-red-100 truncate" title="{{ formatDuration(kpis?.slowestResponse || 0) }}">{{ formatDuration(kpis?.slowestResponse || 0) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time Trend Visualization Placeholder -->
|
||||||
|
<div class="mt-4 h-40 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-200 dark:border-gray-600 flex items-center justify-center">
|
||||||
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Response Time Trend Chart</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Approval Metrics -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h2 class="font-medium text-gray-900 dark:text-gray-100">Approval Metrics</h2>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div class="p-3 bg-green-50 dark:bg-green-900/10 rounded-lg border border-green-100 dark:border-green-800">
|
||||||
|
<div class="text-sm text-green-700 dark:text-green-300 mb-1">Approved</div>
|
||||||
|
<div class="text-xl font-bold text-green-900 dark:text-green-100 truncate" title="{{ kpis?.approved || 0 }}">{{ kpis?.approved || 0 }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 bg-red-50 dark:bg-red-900/10 rounded-lg border border-red-100 dark:border-red-800">
|
||||||
|
<div class="text-sm text-red-700 dark:text-red-300 mb-1">Rejected</div>
|
||||||
|
<div class="text-xl font-bold text-red-900 dark:text-red-100 truncate" title="{{ kpis?.rejected || 0 }}">{{ kpis?.rejected || 0 }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 bg-purple-50 dark:bg-purple-900/10 rounded-lg border border-purple-100 dark:border-purple-800">
|
||||||
|
<div class="text-sm text-purple-700 dark:text-purple-300 mb-1">Approval Rate</div>
|
||||||
|
<div class="text-xl font-bold text-purple-900 dark:text-purple-100 truncate" title="{{ formatPercentage(kpis?.approvalRate || 0) }}">{{ formatPercentage(kpis?.approvalRate || 0) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Approval Visualization Placeholder -->
|
||||||
|
<div class="mt-4 h-40 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-200 dark:border-gray-600 flex items-center justify-center">
|
||||||
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Approval/Rejection Ratio Chart</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Department Performance Table -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||||
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<h2 class="font-medium text-gray-900 dark:text-gray-100">Department Performance</h2>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<thead class="bg-gray-50 dark:bg-gray-900/20">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Department
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Total Requests
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Avg Response Time
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Approval Rate
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Pending
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Overdue %
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
|
<tr v-for="dept in deptPerformance" :key="dept.name" class="hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate max-w-[200px]" :title="dept.name">{{ dept.name }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-900 dark:text-gray-100">{{ dept.total }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-900 dark:text-gray-100">{{ formatDuration(dept.avgResponseHours) }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-900 dark:text-gray-100">{{ formatPercentage(dept.approvalRate) }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-gray-900 dark:text-gray-100">{{ dept.pending }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div
|
||||||
|
class="text-sm font-medium"
|
||||||
|
:class="{
|
||||||
|
'text-red-600 dark:text-red-400': dept.overduePercentage > 10,
|
||||||
|
'text-yellow-600 dark:text-yellow-400': dept.overduePercentage > 0 && dept.overduePercentage <= 10,
|
||||||
|
'text-green-600 dark:text-green-400': dept.overduePercentage === 0
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ formatPercentage(dept.overduePercentage) }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Empty state -->
|
||||||
|
<tr v-if="deptPerformance.length === 0">
|
||||||
|
<td colspan="6" class="px-4 py-8 text-center">
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">No department data available for the selected time period.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</rs-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.admin-dashboard {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
</style>
|
127
pages/dms/check-role.vue
Normal file
127
pages/dms/check-role.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useDmsStore } from '~/stores/dms';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
|
// Define page metadata
|
||||||
|
definePageMeta({
|
||||||
|
title: "Check User Role",
|
||||||
|
middleware: ["auth"],
|
||||||
|
requiresAuth: true,
|
||||||
|
breadcrumb: [
|
||||||
|
{
|
||||||
|
name: "DMS",
|
||||||
|
path: "/dms",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check Role",
|
||||||
|
path: "/dms/check-role",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the DMS store
|
||||||
|
const dmsStore = useDmsStore();
|
||||||
|
|
||||||
|
// Get current user details
|
||||||
|
const currentUser = dmsStore.currentUser;
|
||||||
|
|
||||||
|
// Log user role to console on component mount
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
console.log('Current User Information:');
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
console.log('Name:', currentUser.name);
|
||||||
|
console.log('Email:', currentUser.email);
|
||||||
|
console.log('Role:', currentUser.role);
|
||||||
|
console.log('Department:', currentUser.department);
|
||||||
|
console.log('User ID:', currentUser.id);
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
|
||||||
|
// Check if user has admin permissions
|
||||||
|
const isAdmin = currentUser.role === 'admin';
|
||||||
|
console.log('Has Admin Privileges:', isAdmin ? 'YES' : 'NO');
|
||||||
|
|
||||||
|
// Get detailed permissions (async)
|
||||||
|
dmsStore.getRbacPermissions(currentUser.id).then(permissions => {
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
console.log('Detailed Permissions:');
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
console.log(JSON.stringify(permissions, null, 2));
|
||||||
|
console.log('---------------------------------------');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="check-role-page">
|
||||||
|
<LayoutsBreadcrumb />
|
||||||
|
|
||||||
|
<rs-card class="h-full">
|
||||||
|
<template #body>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-semibold mb-4">User Role Information</h1>
|
||||||
|
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/10 rounded-lg p-5 mb-6 border border-blue-200 dark:border-blue-800">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-medium text-blue-900 dark:text-blue-100">{{ currentUser.name }}</h2>
|
||||||
|
<p class="text-blue-700 dark:text-blue-300">{{ currentUser.email }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-blue-100 dark:border-blue-900/20">
|
||||||
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Role</h3>
|
||||||
|
<p class="text-lg font-semibold text-blue-700 dark:text-blue-400">{{ currentUser.role }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-blue-100 dark:border-blue-900/20">
|
||||||
|
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Department</h3>
|
||||||
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-300">{{ currentUser.department }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">Role information has been logged to the browser console. Open Developer Tools (F12) and check the console tab for detailed permission information.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="text-lg font-medium mb-3">Instructions</h3>
|
||||||
|
<ol class="list-decimal list-inside space-y-2 text-gray-700 dark:text-gray-300">
|
||||||
|
<li>Press <kbd class="px-2 py-1 bg-gray-100 dark:bg-gray-800 rounded border border-gray-300 dark:border-gray-600 text-sm">F12</kbd> to open Developer Tools</li>
|
||||||
|
<li>Navigate to the <strong>Console</strong> tab</li>
|
||||||
|
<li>View your detailed role and permissions information</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-end">
|
||||||
|
<NuxtLink to="/dms" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium transition-colors">
|
||||||
|
Back to DMS
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</rs-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.check-role-page {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,5 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, nextTick } from 'vue';
|
import { ref, onMounted, nextTick, computed } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
// Router
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
// Define page metadata
|
// Define page metadata
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@ -95,6 +99,27 @@ const loadComponents = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if user is superadmin
|
||||||
|
const isSuperAdmin = computed(() => {
|
||||||
|
if (!useDmsStore) return false;
|
||||||
|
const store = useDmsStore();
|
||||||
|
return store.currentUser?.role === 'superadmin';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if user is admin or superadmin
|
||||||
|
const isAdmin = computed(() => {
|
||||||
|
if (!useDmsStore) return false;
|
||||||
|
const store = useDmsStore();
|
||||||
|
return ['admin', 'superadmin'].includes(store.currentUser?.role);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Admin Dashboard link for admins
|
||||||
|
const showAdminDashboard = computed(() => {
|
||||||
|
if (!useDmsStore) return false;
|
||||||
|
const store = useDmsStore();
|
||||||
|
return ['admin', 'superadmin'].includes(store.currentUser?.role);
|
||||||
|
});
|
||||||
|
|
||||||
// Event handlers (placeholder functions for when components load)
|
// Event handlers (placeholder functions for when components load)
|
||||||
const handleItemSelected = (item) => {
|
const handleItemSelected = (item) => {
|
||||||
console.log('Item selected:', item);
|
console.log('Item selected:', item);
|
||||||
@ -146,8 +171,8 @@ onMounted(() => {
|
|||||||
<div v-else class="dms-content h-full flex flex-col">
|
<div v-else class="dms-content h-full flex flex-col">
|
||||||
<!-- Enhanced Access Level Tabs -->
|
<!-- Enhanced Access Level Tabs -->
|
||||||
<div class="access-tabs bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
<div class="access-tabs bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||||
<div class="flex items-center px-6 py-4">
|
<div class="flex flex-col sm:flex-row sm:items-center justify-between px-4 sm:px-6 py-4 gap-3">
|
||||||
<div class="flex space-x-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
<button
|
<button
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
@ -176,6 +201,104 @@ onMounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Access Management Button - Moved to this row -->
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<NuxtLink
|
||||||
|
to="/dms/access-management"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||||
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||||
|
</svg>
|
||||||
|
Access Management
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<!-- Admin Dashboard Button (for admins only) -->
|
||||||
|
<NuxtLink
|
||||||
|
v-if="showAdminDashboard"
|
||||||
|
to="/dms/admin-dashboard"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 border border-purple-300 dark:border-purple-600 rounded-md text-sm font-medium text-purple-700 dark:text-purple-300 bg-white dark:bg-gray-800 hover:bg-purple-50 dark:hover:bg-purple-900/10 transition-colors"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||||
|
</svg>
|
||||||
|
Admin Dashboard
|
||||||
|
</NuxtLink>
|
||||||
|
|
||||||
|
<!-- Switch Role Button -->
|
||||||
|
<NuxtLink
|
||||||
|
to="/dms/switch-roles"
|
||||||
|
class="inline-flex items-center px-3 py-1.5 border border-indigo-300 dark:border-indigo-600 rounded-md text-sm font-medium text-indigo-700 dark:text-indigo-300 bg-white dark:bg-gray-800 hover:bg-indigo-50 dark:hover:bg-indigo-900/10 transition-colors"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
Switch Role
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Superadmin KPI Dashboard -->
|
||||||
|
<div v-if="isSuperAdmin" class="kpi-dashboard bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 sm:px-6 py-3 overflow-hidden">
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-900/20 flex items-center justify-center mr-3 flex-shrink-0">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">450</div>
|
||||||
|
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Total Documents</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-green-100 dark:bg-green-900/20 flex items-center justify-center mr-3 flex-shrink-0">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">12</div>
|
||||||
|
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">New Documents Today</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-yellow-100 dark:bg-yellow-900/20 flex items-center justify-center mr-3 flex-shrink-0">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-yellow-600 dark:text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">5</div>
|
||||||
|
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Pending Access Requests</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-700 rounded-lg p-3 border border-gray-200 dark:border-gray-600 shadow-sm">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-8 h-8 rounded-lg bg-purple-100 dark:bg-purple-900/20 flex items-center justify-center mr-3 flex-shrink-0">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div class="text-xl font-bold text-gray-900 dark:text-gray-100 truncate">32</div>
|
||||||
|
<div class="text-xs text-gray-600 dark:text-gray-400 truncate">Active Users</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -188,31 +311,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<!-- Access Management Button -->
|
<!-- Role Management button has been removed -->
|
||||||
<NuxtLink
|
|
||||||
to="/dms/access-management"
|
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
|
||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
||||||
</svg>
|
|
||||||
Access Management
|
|
||||||
</NuxtLink>
|
|
||||||
|
|
||||||
<!-- Role Management Button -->
|
|
||||||
<NuxtLink
|
|
||||||
to="/dms/role-management"
|
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
|
||||||
<circle cx="9" cy="7" r="4"></circle>
|
|
||||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
|
|
||||||
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
|
|
||||||
</svg>
|
|
||||||
Role Management
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
270
pages/dms/switch-roles.vue
Normal file
270
pages/dms/switch-roles.vue
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useDmsStore } from '~/stores/dms';
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
// Define page metadata
|
||||||
|
definePageMeta({
|
||||||
|
title: "Switch User Role",
|
||||||
|
middleware: ["auth"],
|
||||||
|
requiresAuth: true,
|
||||||
|
breadcrumb: [
|
||||||
|
{
|
||||||
|
name: "DMS",
|
||||||
|
path: "/dms",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Switch Role",
|
||||||
|
path: "/dms/switch-roles",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the DMS store and router
|
||||||
|
const dmsStore = useDmsStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Original user backup
|
||||||
|
const originalUser = ref(null);
|
||||||
|
const currentRole = ref('');
|
||||||
|
const message = ref('');
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
// Get available roles from the store
|
||||||
|
const availableRoles = computed(() => {
|
||||||
|
return dmsStore.systemRoles || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check current role on mount
|
||||||
|
onMounted(() => {
|
||||||
|
// Store original user for restoration later
|
||||||
|
originalUser.value = { ...dmsStore.currentUser };
|
||||||
|
currentRole.value = dmsStore.currentUser.role;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get role details by ID
|
||||||
|
const getRoleById = (roleId) => {
|
||||||
|
return availableRoles.value.find(role => role.id === roleId) || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Switch to a specific role
|
||||||
|
const switchToRole = (roleId) => {
|
||||||
|
if (currentRole.value === roleId) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
message.value = `Switching to ${getRoleById(roleId).name} role...`;
|
||||||
|
|
||||||
|
// User data based on role
|
||||||
|
const userData = {
|
||||||
|
superadmin: {
|
||||||
|
id: 'superadmin1',
|
||||||
|
name: 'Super Admin User',
|
||||||
|
email: 'superadmin@example.com',
|
||||||
|
role: 'superadmin',
|
||||||
|
department: 'IT Department'
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
id: 'admin1',
|
||||||
|
name: 'Admin User',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
role: 'admin',
|
||||||
|
department: 'IT Department'
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
id: 'user1',
|
||||||
|
name: 'Regular User',
|
||||||
|
email: 'user@example.com',
|
||||||
|
role: 'user',
|
||||||
|
department: 'General Department'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keep original fields that we don't want to change
|
||||||
|
const originalFields = {};
|
||||||
|
if (originalUser.value && originalUser.value.role === roleId) {
|
||||||
|
// If switching back to the original role, use all original data
|
||||||
|
dmsStore.currentUser = { ...originalUser.value };
|
||||||
|
} else {
|
||||||
|
// Otherwise use the role-specific data
|
||||||
|
dmsStore.currentUser = {
|
||||||
|
...originalFields,
|
||||||
|
...userData[roleId]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update current role display
|
||||||
|
currentRole.value = roleId;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
message.value = `Successfully switched to ${getRoleById(roleId).name} role!`;
|
||||||
|
isLoading.value = false;
|
||||||
|
}, 800);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Switch back to original role
|
||||||
|
const switchToOriginal = () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
message.value = 'Switching back to original role...';
|
||||||
|
|
||||||
|
if (originalUser.value) {
|
||||||
|
dmsStore.currentUser = { ...originalUser.value };
|
||||||
|
currentRole.value = originalUser.value.role;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
message.value = 'Successfully switched back to original role!';
|
||||||
|
isLoading.value = false;
|
||||||
|
}, 800);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Navigate back to DMS homepage
|
||||||
|
const goToDms = () => {
|
||||||
|
router.push('/dms');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="switch-role-page">
|
||||||
|
<LayoutsBreadcrumb />
|
||||||
|
|
||||||
|
<rs-card class="h-full">
|
||||||
|
<template #body>
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-semibold mb-4">Switch User Role</h1>
|
||||||
|
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/10 rounded-lg p-5 mb-6 border border-blue-200 dark:border-blue-800">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center mr-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-xl font-medium text-blue-900 dark:text-blue-100">
|
||||||
|
Current Role: <span class="font-bold">{{ getRoleById(currentRole).name || currentRole }}</span>
|
||||||
|
</h2>
|
||||||
|
<p class="text-blue-700 dark:text-blue-300">
|
||||||
|
This tool allows you to temporarily switch your role to view the system from different perspectives
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Animated Status Message -->
|
||||||
|
<div v-if="message" class="mb-4 p-4 rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div v-if="isLoading" class="mr-3 h-4 w-4 rounded-full border-2 border-blue-600 border-t-transparent animate-spin"></div>
|
||||||
|
<div v-else class="mr-3 h-4 w-4 rounded-full bg-green-500"></div>
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<!-- Superadmin Role Card -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-5 border border-purple-100 dark:border-purple-900/20">
|
||||||
|
<div class="flex items-center mb-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center mr-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-9.618 5.04L12 21.012l9.618-13.028A11.955 11.955 0 0112 2.944z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-purple-600 dark:text-purple-400">Superadmin Role</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4 text-sm h-20 overflow-auto">
|
||||||
|
Full system access with complete control over users, settings, and content. Ability to manage all aspects of the system including user roles and permissions.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
@click="switchToRole('superadmin')"
|
||||||
|
class="w-full px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-md text-sm font-medium transition-colors"
|
||||||
|
:disabled="currentRole === 'superadmin' || isLoading"
|
||||||
|
:class="{'opacity-50': currentRole === 'superadmin' || isLoading}"
|
||||||
|
>
|
||||||
|
Switch to Superadmin
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin Role Card -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-5 border border-blue-100 dark:border-blue-900/20">
|
||||||
|
<div class="flex items-center mb-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center mr-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-blue-600 dark:text-blue-400">Admin Role</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4 text-sm h-20 overflow-auto">
|
||||||
|
Administrative access with ability to manage content, approve requests, and view performance metrics. Access to dashboards and management tools.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
@click="switchToRole('admin')"
|
||||||
|
class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md text-sm font-medium transition-colors"
|
||||||
|
:disabled="currentRole === 'admin' || isLoading"
|
||||||
|
:class="{'opacity-50': currentRole === 'admin' || isLoading}"
|
||||||
|
>
|
||||||
|
Switch to Admin
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User Role Card -->
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-5 border border-green-100 dark:border-green-900/20">
|
||||||
|
<div class="flex items-center mb-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center mr-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-green-600 dark:text-green-400">User Role</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 mb-4 text-sm h-20 overflow-auto">
|
||||||
|
Standard user access with ability to view permitted documents, request access to restricted content, and perform basic operations within granted permissions.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
@click="switchToRole('user')"
|
||||||
|
class="w-full px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md text-sm font-medium transition-colors"
|
||||||
|
:disabled="currentRole === 'user' || isLoading"
|
||||||
|
:class="{'opacity-50': currentRole === 'user' || isLoading}"
|
||||||
|
>
|
||||||
|
Switch to User
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Original Role Button -->
|
||||||
|
<div v-if="originalUser && currentRole !== originalUser.role" class="mt-4">
|
||||||
|
<button
|
||||||
|
@click="switchToOriginal"
|
||||||
|
class="w-full px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md text-sm font-medium transition-colors"
|
||||||
|
:disabled="isLoading"
|
||||||
|
>
|
||||||
|
Restore Original Role ({{ getRoleById(originalUser.role).name || originalUser.role }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 bg-yellow-50 dark:bg-yellow-900/10 rounded-lg p-5 border border-yellow-200 dark:border-yellow-800">
|
||||||
|
<h3 class="text-lg font-medium mb-3 text-yellow-800 dark:text-yellow-400">Important Note</h3>
|
||||||
|
<p class="text-yellow-700 dark:text-yellow-300 mb-4">
|
||||||
|
This role switch is temporary and will reset when you refresh the page. This tool is for testing and demonstration purposes only.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-center">
|
||||||
|
<button
|
||||||
|
@click="goToDms"
|
||||||
|
class="px-6 py-3 bg-yellow-600 hover:bg-yellow-700 text-white rounded-md text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
|
Return to DMS with New Role
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</rs-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.switch-role-page {
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
</style>
|
103
stores/dms.js
103
stores/dms.js
@ -12,10 +12,32 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
id: 'user1',
|
id: 'user1',
|
||||||
name: 'Aiman Fakhrullah',
|
name: 'Aiman Fakhrullah',
|
||||||
email: 'aiman@example.com',
|
email: 'aiman@example.com',
|
||||||
role: 'engineer', // engineer, admin, manager, etc.
|
role: 'user', // Role can be 'superadmin', 'admin', or 'user'
|
||||||
department: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu'
|
department: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// System roles
|
||||||
|
systemRoles: [
|
||||||
|
{
|
||||||
|
id: 'superadmin',
|
||||||
|
name: 'Super Administrator',
|
||||||
|
description: 'Full system access with ability to manage all settings, users, and content',
|
||||||
|
color: 'purple'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'admin',
|
||||||
|
name: 'Administrator',
|
||||||
|
description: 'Administrative access to manage content and some system settings',
|
||||||
|
color: 'blue'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
name: 'User',
|
||||||
|
description: 'Standard user access for viewing and interacting with content based on permissions',
|
||||||
|
color: 'green'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
// Cabinet access types
|
// Cabinet access types
|
||||||
cabinetAccessTypes: [
|
cabinetAccessTypes: [
|
||||||
{ id: 'public', name: 'Public Access', icon: 'check-circle', color: 'green' },
|
{ id: 'public', name: 'Public Access', icon: 'check-circle', color: 'green' },
|
||||||
@ -1667,6 +1689,19 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
// This is a placeholder for the actual Authentik integration
|
// This is a placeholder for the actual Authentik integration
|
||||||
// In a real implementation, this would make API calls to Authentik
|
// In a real implementation, this would make API calls to Authentik
|
||||||
|
|
||||||
|
if (username === 'superadmin' && password === 'password') {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: 'superadmin1',
|
||||||
|
name: 'Super Admin User',
|
||||||
|
email: 'superadmin@example.com',
|
||||||
|
role: 'superadmin',
|
||||||
|
department: 'IT Department'
|
||||||
|
},
|
||||||
|
token: 'sample-authentik-token'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (username === 'admin' && password === 'password') {
|
if (username === 'admin' && password === 'password') {
|
||||||
return {
|
return {
|
||||||
user: {
|
user: {
|
||||||
@ -1686,8 +1721,8 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
id: 'user1',
|
id: 'user1',
|
||||||
name: 'Aiman Fakhrullah',
|
name: 'Aiman Fakhrullah',
|
||||||
email: 'aiman@example.com',
|
email: 'aiman@example.com',
|
||||||
role: 'engineer',
|
role: 'user',
|
||||||
department: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu'
|
department: 'General Department'
|
||||||
},
|
},
|
||||||
token: 'sample-authentik-token'
|
token: 'sample-authentik-token'
|
||||||
};
|
};
|
||||||
@ -1705,6 +1740,39 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
// In a real implementation, this would fetch RBAC permissions from Authentik
|
// In a real implementation, this would fetch RBAC permissions from Authentik
|
||||||
|
|
||||||
const permissions = {
|
const permissions = {
|
||||||
|
'superadmin1': {
|
||||||
|
roles: ['superadmin'],
|
||||||
|
permissions: {
|
||||||
|
documents: {
|
||||||
|
view: true,
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
approve: true,
|
||||||
|
reject: true,
|
||||||
|
download: true
|
||||||
|
},
|
||||||
|
cabinets: {
|
||||||
|
view: true,
|
||||||
|
create: true,
|
||||||
|
edit: true,
|
||||||
|
delete: true
|
||||||
|
},
|
||||||
|
accessRequests: {
|
||||||
|
approve: true,
|
||||||
|
reject: true,
|
||||||
|
viewAll: true
|
||||||
|
},
|
||||||
|
systemSettings: {
|
||||||
|
manage: true
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
manage: true
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
manage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
'admin1': {
|
'admin1': {
|
||||||
roles: ['admin'],
|
roles: ['admin'],
|
||||||
permissions: {
|
permissions: {
|
||||||
@ -1726,11 +1794,20 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
approve: true,
|
approve: true,
|
||||||
reject: true,
|
reject: true,
|
||||||
viewAll: true
|
viewAll: true
|
||||||
|
},
|
||||||
|
systemSettings: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
manage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'user1': {
|
'user1': {
|
||||||
roles: ['engineer'],
|
roles: ['user'],
|
||||||
permissions: {
|
permissions: {
|
||||||
documents: {
|
documents: {
|
||||||
view: true,
|
view: true,
|
||||||
@ -1750,6 +1827,15 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
approve: false,
|
approve: false,
|
||||||
reject: false,
|
reject: false,
|
||||||
viewAll: false
|
viewAll: false
|
||||||
|
},
|
||||||
|
systemSettings: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
manage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1776,6 +1862,15 @@ export const useDmsStore = defineStore('dms', {
|
|||||||
approve: false,
|
approve: false,
|
||||||
reject: false,
|
reject: false,
|
||||||
viewAll: false
|
viewAll: false
|
||||||
|
},
|
||||||
|
systemSettings: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
manage: false
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
manage: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user