- {{ settingsCategories.find(c => c.id === activeCategory)?.icon }}
+
+
+
+
🔄 Workflow & Automation
+
+
+
+
Approval Workflows
+
+
+
+ Enable Approval Workflows
+
+
+ Default Approval Flow
+
+ Department Head Approval
+ Legal Review
+ Finance Approval
+ Director Sign-off
+
+
+
+
+
+
+
+
Notification Settings
+
-
- {{ settingsCategories.find(c => c.id === activeCategory)?.name }}
-
-
- Settings for this category are being developed and will be available in the next update.
-
diff --git a/pages/dms/switch-roles.vue b/pages/dms/switch-roles.vue
new file mode 100644
index 0000000..40d5899
--- /dev/null
+++ b/pages/dms/switch-roles.vue
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
Switch User Role
+
+
+
+
+
+
+ Current Role: {{ getRoleById(currentRole).name || currentRole }}
+
+
+ This tool allows you to temporarily switch your role to view the system from different perspectives
+
+
+
+
+
+
+
+
+
+
+
+
+ Full system access with complete control over users, settings, and content. Ability to manage all aspects of the system including user roles and permissions.
+
+
+ Switch to Superadmin
+
+
+
+
+
+
+
+ Administrative access with ability to manage content, approve requests, and view performance metrics. Access to dashboards and management tools.
+
+
+ Switch to Admin
+
+
+
+
+
+
+
+ Standard user access with ability to view permitted documents, request access to restricted content, and perform basic operations within granted permissions.
+
+
+ Switch to User
+
+
+
+
+
+
+
+ Restore Original Role ({{ getRoleById(originalUser.role).name || originalUser.role }})
+
+
+
+
+
+
Important Note
+
+ This role switch is temporary and will reset when you refresh the page. This tool is for testing and demonstration purposes only.
+
+
+
+
+ Return to DMS with New Role
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index c638989..f481133 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -41,7 +41,11 @@ model department {
org_id Int
cabinets cabinets[]
organization organization @relation(fields: [org_id], references: [org_id], onDelete: Cascade, onUpdate: NoAction, map: "department_organization_FK")
+<<<<<<< HEAD
user user[]
+=======
+ users sys_user[]
+>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
@@index([org_id], map: "department_organization_FK")
}
@@ -58,6 +62,26 @@ model cabinets {
department department? @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "cabinets_department_FK")
@@index([dp_id], map: "cabinets_department_FK")
+<<<<<<< HEAD
+=======
+}
+
+model sys_user {
+ su_id Int @id @default(autoincrement())
+ su_username String @unique(map: "sys_user_unique") @db.VarChar(100)
+ su_name String @db.VarChar(255)
+ su_nric Int @unique(map: "sys_user_unique_1")
+ su_dob DateTime @db.Date
+ su_email String? @db.VarChar(255)
+ su_password String @db.VarChar(255)
+ dp_id Int
+ su_active Int? @default(1)
+ su_lock Int? @default(0)
+ su_org_id Int
+ department department @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "sys_user_department_FK")
+
+ @@index([dp_id], map: "sys_user_department_FK")
+>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
}
model role {
@@ -71,6 +95,7 @@ model role {
}
model user {
+<<<<<<< HEAD
userID Int @id @default(autoincrement())
userSecretKey String? @db.VarChar(255)
userUsername String? @db.VarChar(255)
@@ -87,6 +112,20 @@ model user {
userrole userrole[]
@@index([dp_id], map: "user_department_FK")
+=======
+ userID Int @id @default(autoincrement())
+ userSecretKey String? @db.VarChar(255)
+ userUsername String? @db.VarChar(255)
+ userPassword String? @db.VarChar(255)
+ userFullName String? @db.VarChar(255)
+ userEmail String? @db.VarChar(255)
+ userPhone String? @db.VarChar(255)
+ userStatus String? @db.VarChar(255)
+ userCreatedDate DateTime? @db.DateTime(0)
+ userModifiedDate DateTime? @db.DateTime(0)
+ audit audit[]
+ userrole userrole[]
+>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
}
model userrole {
@@ -164,6 +203,7 @@ model dms_settings {
}
model site_settings {
+<<<<<<< HEAD
settingID Int @id @default(autoincrement())
siteName String? @default("corradAF") @db.VarChar(255)
siteNameFontSize Int? @default(18)
@@ -191,4 +231,107 @@ model site_settings {
seoFacebookPixel String? @db.VarChar(255)
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
+=======
+ settingID Int @id @default(autoincrement())
+ siteName String? @default("corradAF") @db.VarChar(255)
+ siteNameFontSize Int? @default(18)
+ siteDescription String? @db.Text
+ siteLogo String? @db.VarChar(500)
+ siteLoadingLogo String? @db.VarChar(500)
+ siteFavicon String? @db.VarChar(500)
+ siteLoginLogo String? @db.VarChar(500)
+ showSiteNameInHeader Boolean? @default(true)
+ customCSS String? @db.LongText
+ themeMode String? @default("biasa") @db.VarChar(100)
+ customThemeFile String? @db.VarChar(500)
+ currentFont String? @db.VarChar(100)
+ fontSource String? @db.VarChar(100)
+ seoTitle String? @db.VarChar(255)
+ seoDescription String? @db.Text
+ seoKeywords String? @db.Text
+ seoAuthor String? @db.VarChar(255)
+ seoOgImage String? @db.VarChar(500)
+ seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
+ seoCanonicalUrl String? @db.VarChar(500)
+ seoRobots String? @default("index, follow") @db.VarChar(100)
+ seoGoogleAnalytics String? @db.VarChar(255)
+ seoGoogleTagManager String? @db.VarChar(255)
+ seoFacebookPixel String? @db.VarChar(255)
+ settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
+ settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
+}
+
+model dms_settings {
+ settingID Int @id @default(autoincrement())
+ // User & Access Management
+ userRoles String? @db.Text
+ rbacEnabled Boolean? @default(true)
+ userGroups String? @db.Text
+ permissionView Boolean? @default(true)
+ permissionEdit Boolean? @default(true)
+ permissionDelete Boolean? @default(false)
+ permissionDownload Boolean? @default(true)
+ permissionShare Boolean? @default(true)
+ ssoEnabled Boolean? @default(false)
+ mfaRequired Boolean? @default(false)
+ ldapIntegration Boolean? @default(false)
+ sessionTimeout Int? @default(8)
+
+ // Document & Folder Settings
+ folderMaxDepth Int? @default(5)
+ folderDefaultStructure String? @db.Text
+ folderTemplates String? @db.Text
+ namingAutoGenerate Boolean? @default(true)
+ namingMandatoryFields String? @db.Text
+ namingPattern String? @default("{department}_{title}_{date}") @db.VarChar(255)
+ retentionEnabled Boolean? @default(true)
+ retentionDefaultDays Int? @default(2555)
+ retentionArchiveBeforeDelete Boolean? @default(true)
+ versionControlEnabled Boolean? @default(true)
+ versionControlMaxVersions Int? @default(10)
+ versionControlAutoVersioning Boolean? @default(true)
+
+ // Metadata & Tagging
+ metadataCustomFields String? @db.LongText
+ taggingPredefinedTags String? @db.Text
+ taggingUserGeneratedTags Boolean? @default(true)
+ taggingTagSuggestions Boolean? @default(true)
+ classificationAutoEnabled Boolean? @default(true)
+ classificationRules String? @db.Text
+
+ // Workflow & Automation
+ workflowApprovalEnabled Boolean? @default(true)
+ workflowDefaultFlow String? @default("department-head-approval") @db.VarChar(255)
+ workflowCustomFlows String? @db.Text
+ notificationEmail Boolean? @default(true)
+ notificationInApp Boolean? @default(true)
+ notificationUploadAlerts Boolean? @default(true)
+ notificationDeadlineReminders Boolean? @default(true)
+ automationTriggers String? @db.Text
+ automationActions String? @db.Text
+
+ // Upload & Storage Settings
+ uploadAllowedFileTypes String? @db.Text
+ uploadBlockedFileTypes String? @db.Text
+ uploadFileSizeLimit Int? @default(100)
+ uploadQuotaPerUser Int? @default(5000)
+ uploadQuotaPerGroup Int? @default(50000)
+ uploadQuotaPerProject Int? @default(100000)
+ storageType String? @default("local") @db.VarChar(100)
+ storagePath String? @default("/var/uploads/edms") @db.VarChar(500)
+ storageBackupEnabled Boolean? @default(true)
+ storageCompressionEnabled Boolean? @default(false)
+
+ // System Settings
+ systemTimezone String? @default("Asia/Kuala_Lumpur") @db.VarChar(100)
+ systemBackupSchedule String? @default("daily") @db.VarChar(100)
+ systemLogLevel String? @default("info") @db.VarChar(100)
+ systemMaintenanceMode Boolean? @default(false)
+ systemAutoUpdates Boolean? @default(false)
+ systemMonitoring Boolean? @default(true)
+ systemPerformanceMetrics Boolean? @default(true)
+
+ settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
+ settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
+>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
}
diff --git a/server/api/dms/settings.js b/server/api/dms/settings.js
new file mode 100644
index 0000000..3261bdf
--- /dev/null
+++ b/server/api/dms/settings.js
@@ -0,0 +1,350 @@
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ const method = getMethod(event);
+
+ try {
+ if (method === "GET") {
+ // Get DMS settings
+ let settings = await prisma.dms_settings.findFirst({
+ orderBy: { settingID: "desc" },
+ });
+
+ // If no settings exist, create default ones
+ if (!settings) {
+ settings = await prisma.dms_settings.create({
+ data: {
+ settingCreatedDate: new Date(),
+ settingModifiedDate: new Date(),
+ },
+ });
+ }
+
+ // Transform database fields to frontend structure
+ const transformedSettings = {
+ // User & Access Management
+ access: {
+ userRoles: settings.userRoles ? settings.userRoles.split(',') : ['Admin', 'Editor', 'Viewer', 'Uploader'],
+ rbacEnabled: settings.rbacEnabled ?? true,
+ userGroups: settings.userGroups ? settings.userGroups.split(',') : ['HR Department', 'Finance', 'IT', 'Legal'],
+ permissions: {
+ view: settings.permissionView ?? true,
+ edit: settings.permissionEdit ?? true,
+ delete: settings.permissionDelete ?? false,
+ download: settings.permissionDownload ?? true,
+ share: settings.permissionShare ?? true
+ },
+ authentication: {
+ ssoEnabled: settings.ssoEnabled ?? false,
+ mfaRequired: settings.mfaRequired ?? false,
+ ldapIntegration: settings.ldapIntegration ?? false,
+ sessionTimeout: settings.sessionTimeout ?? 8
+ }
+ },
+
+ // Document & Folder Settings
+ documents: {
+ folderHierarchy: {
+ maxDepth: settings.folderMaxDepth ?? 5,
+ defaultStructure: settings.folderDefaultStructure ? settings.folderDefaultStructure.split(',') : ['Department', 'Project', 'Category', 'Year'],
+ folderTemplates: settings.folderTemplates ? settings.folderTemplates.split(',') : ['Standard', 'Project-based', 'Department-based']
+ },
+ namingConventions: {
+ autoGenerate: settings.namingAutoGenerate ?? true,
+ mandatoryFields: settings.namingMandatoryFields ? settings.namingMandatoryFields.split(',') : ['title', 'department', 'date'],
+ pattern: settings.namingPattern ?? '{department}_{title}_{date}'
+ },
+ retention: {
+ enabled: settings.retentionEnabled ?? true,
+ defaultDays: settings.retentionDefaultDays ?? 2555,
+ archiveBeforeDelete: settings.retentionArchiveBeforeDelete ?? true
+ },
+ versionControl: {
+ enabled: settings.versionControlEnabled ?? true,
+ maxVersions: settings.versionControlMaxVersions ?? 10,
+ autoVersioning: settings.versionControlAutoVersioning ?? true
+ }
+ },
+
+ // Metadata & Tagging
+ metadata: {
+ customFields: settings.metadataCustomFields ? JSON.parse(settings.metadataCustomFields) : [
+ { name: 'Department', type: 'dropdown', required: true },
+ { name: 'Priority', type: 'select', required: false },
+ { name: 'Project Code', type: 'text', required: true },
+ { name: 'Review Date', type: 'date', required: false }
+ ],
+ tagging: {
+ predefinedTags: settings.taggingPredefinedTags ? settings.taggingPredefinedTags.split(',') : ['urgent', 'confidential', 'public', 'draft', 'final'],
+ userGeneratedTags: settings.taggingUserGeneratedTags ?? true,
+ tagSuggestions: settings.taggingTagSuggestions ?? true
+ },
+ classification: {
+ autoClassification: settings.classificationAutoEnabled ?? true,
+ rules: settings.classificationRules ? settings.classificationRules.split(',') : ['confidential-keywords', 'department-based', 'file-type']
+ }
+ },
+
+ // Workflow & Automation
+ workflow: {
+ approvalFlows: {
+ enabled: settings.workflowApprovalEnabled ?? true,
+ defaultFlow: settings.workflowDefaultFlow ?? 'department-head-approval',
+ customFlows: settings.workflowCustomFlows ? settings.workflowCustomFlows.split(',') : ['legal-review', 'finance-approval', 'director-sign-off']
+ },
+ notifications: {
+ emailNotifications: settings.notificationEmail ?? true,
+ inAppNotifications: settings.notificationInApp ?? true,
+ uploadAlerts: settings.notificationUploadAlerts ?? true,
+ deadlineReminders: settings.notificationDeadlineReminders ?? true
+ },
+ automation: {
+ triggers: settings.automationTriggers ? settings.automationTriggers.split(',') : ['document-uploaded', 'approval-completed', 'deadline-reached'],
+ actions: settings.automationActions ? settings.automationActions.split(',') : ['move-to-folder', 'send-notification', 'create-task']
+ }
+ },
+
+ // Upload & Storage Settings
+ upload: {
+ fileTypes: {
+ allowed: settings.uploadAllowedFileTypes ? settings.uploadAllowedFileTypes.split(',') : ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
+ blocked: settings.uploadBlockedFileTypes ? settings.uploadBlockedFileTypes.split(',') : ['exe', 'bat', 'cmd']
+ },
+ fileSizeLimit: settings.uploadFileSizeLimit ?? 100,
+ quotas: {
+ perUser: settings.uploadQuotaPerUser ?? 5000,
+ perGroup: settings.uploadQuotaPerGroup ?? 50000,
+ perProject: settings.uploadQuotaPerProject ?? 100000
+ },
+ storage: {
+ type: settings.storageType ?? 'local',
+ path: settings.storagePath ?? '/var/uploads/edms',
+ backupEnabled: settings.storageBackupEnabled ?? true,
+ compressionEnabled: settings.storageCompressionEnabled ?? false
+ }
+ },
+
+ // System Settings
+ system: {
+ timezone: settings.systemTimezone ?? 'Asia/Kuala_Lumpur',
+ backupSchedule: settings.systemBackupSchedule ?? 'daily',
+ logLevel: settings.systemLogLevel ?? 'info',
+ maintenanceMode: settings.systemMaintenanceMode ?? false,
+ autoUpdates: settings.systemAutoUpdates ?? false,
+ systemMonitoring: settings.systemMonitoring ?? true,
+ performanceMetrics: settings.systemPerformanceMetrics ?? true
+ }
+ };
+
+ return {
+ statusCode: 200,
+ message: "Success",
+ data: transformedSettings,
+ };
+ }
+
+ if (method === "POST") {
+ let body;
+ try {
+ body = await readBody(event);
+ } catch (bodyError) {
+ console.error("Error reading request body:", bodyError);
+ return {
+ statusCode: 400,
+ message: "Invalid request body",
+ error: bodyError.message,
+ };
+ }
+
+ // Validate required fields
+ if (!body || typeof body !== 'object') {
+ return {
+ statusCode: 400,
+ message: "Request body must be a valid JSON object",
+ };
+ }
+
+ // Check if settings exist
+ const existingSettings = await prisma.dms_settings.findFirst();
+
+ // Transform frontend structure to database fields
+ const dbData = {
+ settingModifiedDate: new Date()
+ };
+
+ // User & Access Management
+ if (body.access) {
+ if (body.access.userRoles) dbData.userRoles = body.access.userRoles.join(',');
+ if (body.access.rbacEnabled !== undefined) dbData.rbacEnabled = body.access.rbacEnabled;
+ if (body.access.userGroups) dbData.userGroups = body.access.userGroups.join(',');
+ if (body.access.permissions) {
+ if (body.access.permissions.view !== undefined) dbData.permissionView = body.access.permissions.view;
+ if (body.access.permissions.edit !== undefined) dbData.permissionEdit = body.access.permissions.edit;
+ if (body.access.permissions.delete !== undefined) dbData.permissionDelete = body.access.permissions.delete;
+ if (body.access.permissions.download !== undefined) dbData.permissionDownload = body.access.permissions.download;
+ if (body.access.permissions.share !== undefined) dbData.permissionShare = body.access.permissions.share;
+ }
+ if (body.access.authentication) {
+ if (body.access.authentication.ssoEnabled !== undefined) dbData.ssoEnabled = body.access.authentication.ssoEnabled;
+ if (body.access.authentication.mfaRequired !== undefined) dbData.mfaRequired = body.access.authentication.mfaRequired;
+ if (body.access.authentication.ldapIntegration !== undefined) dbData.ldapIntegration = body.access.authentication.ldapIntegration;
+ if (body.access.authentication.sessionTimeout !== undefined) dbData.sessionTimeout = body.access.authentication.sessionTimeout;
+ }
+ }
+
+ // Document & Folder Settings
+ if (body.documents) {
+ if (body.documents.folderHierarchy) {
+ if (body.documents.folderHierarchy.maxDepth !== undefined) dbData.folderMaxDepth = body.documents.folderHierarchy.maxDepth;
+ if (body.documents.folderHierarchy.defaultStructure) dbData.folderDefaultStructure = body.documents.folderHierarchy.defaultStructure.join(',');
+ if (body.documents.folderHierarchy.folderTemplates) dbData.folderTemplates = body.documents.folderHierarchy.folderTemplates.join(',');
+ }
+ if (body.documents.namingConventions) {
+ if (body.documents.namingConventions.autoGenerate !== undefined) dbData.namingAutoGenerate = body.documents.namingConventions.autoGenerate;
+ if (body.documents.namingConventions.mandatoryFields) dbData.namingMandatoryFields = body.documents.namingConventions.mandatoryFields.join(',');
+ if (body.documents.namingConventions.pattern !== undefined) dbData.namingPattern = body.documents.namingConventions.pattern;
+ }
+ if (body.documents.retention) {
+ if (body.documents.retention.enabled !== undefined) dbData.retentionEnabled = body.documents.retention.enabled;
+ if (body.documents.retention.defaultDays !== undefined) dbData.retentionDefaultDays = body.documents.retention.defaultDays;
+ if (body.documents.retention.archiveBeforeDelete !== undefined) dbData.retentionArchiveBeforeDelete = body.documents.retention.archiveBeforeDelete;
+ }
+ if (body.documents.versionControl) {
+ if (body.documents.versionControl.enabled !== undefined) dbData.versionControlEnabled = body.documents.versionControl.enabled;
+ if (body.documents.versionControl.maxVersions !== undefined) dbData.versionControlMaxVersions = body.documents.versionControl.maxVersions;
+ if (body.documents.versionControl.autoVersioning !== undefined) dbData.versionControlAutoVersioning = body.documents.versionControl.autoVersioning;
+ }
+ }
+
+ // Metadata & Tagging
+ if (body.metadata) {
+ if (body.metadata.customFields) dbData.metadataCustomFields = JSON.stringify(body.metadata.customFields);
+ if (body.metadata.tagging) {
+ if (body.metadata.tagging.predefinedTags) dbData.taggingPredefinedTags = body.metadata.tagging.predefinedTags.join(',');
+ if (body.metadata.tagging.userGeneratedTags !== undefined) dbData.taggingUserGeneratedTags = body.metadata.tagging.userGeneratedTags;
+ if (body.metadata.tagging.tagSuggestions !== undefined) dbData.taggingTagSuggestions = body.metadata.tagging.tagSuggestions;
+ }
+ if (body.metadata.classification) {
+ if (body.metadata.classification.autoClassification !== undefined) dbData.classificationAutoEnabled = body.metadata.classification.autoClassification;
+ if (body.metadata.classification.rules) dbData.classificationRules = body.metadata.classification.rules.join(',');
+ }
+ }
+
+ // Workflow & Automation
+ if (body.workflow) {
+ if (body.workflow.approvalFlows) {
+ if (body.workflow.approvalFlows.enabled !== undefined) dbData.workflowApprovalEnabled = body.workflow.approvalFlows.enabled;
+ if (body.workflow.approvalFlows.defaultFlow !== undefined) dbData.workflowDefaultFlow = body.workflow.approvalFlows.defaultFlow;
+ if (body.workflow.approvalFlows.customFlows) dbData.workflowCustomFlows = body.workflow.approvalFlows.customFlows.join(',');
+ }
+ if (body.workflow.notifications) {
+ if (body.workflow.notifications.emailNotifications !== undefined) dbData.notificationEmail = body.workflow.notifications.emailNotifications;
+ if (body.workflow.notifications.inAppNotifications !== undefined) dbData.notificationInApp = body.workflow.notifications.inAppNotifications;
+ if (body.workflow.notifications.uploadAlerts !== undefined) dbData.notificationUploadAlerts = body.workflow.notifications.uploadAlerts;
+ if (body.workflow.notifications.deadlineReminders !== undefined) dbData.notificationDeadlineReminders = body.workflow.notifications.deadlineReminders;
+ }
+ if (body.workflow.automation) {
+ if (body.workflow.automation.triggers) dbData.automationTriggers = body.workflow.automation.triggers.join(',');
+ if (body.workflow.automation.actions) dbData.automationActions = body.workflow.automation.actions.join(',');
+ }
+ }
+
+ // Upload & Storage Settings
+ if (body.upload) {
+ if (body.upload.fileTypes) {
+ if (body.upload.fileTypes.allowed) dbData.uploadAllowedFileTypes = body.upload.fileTypes.allowed.join(',');
+ if (body.upload.fileTypes.blocked) dbData.uploadBlockedFileTypes = body.upload.fileTypes.blocked.join(',');
+ }
+ if (body.upload.fileSizeLimit !== undefined) dbData.uploadFileSizeLimit = body.upload.fileSizeLimit;
+ if (body.upload.quotas) {
+ if (body.upload.quotas.perUser !== undefined) dbData.uploadQuotaPerUser = body.upload.quotas.perUser;
+ if (body.upload.quotas.perGroup !== undefined) dbData.uploadQuotaPerGroup = body.upload.quotas.perGroup;
+ if (body.upload.quotas.perProject !== undefined) dbData.uploadQuotaPerProject = body.upload.quotas.perProject;
+ }
+ if (body.upload.storage) {
+ if (body.upload.storage.type !== undefined) dbData.storageType = body.upload.storage.type;
+ if (body.upload.storage.path !== undefined) dbData.storagePath = body.upload.storage.path;
+ if (body.upload.storage.backupEnabled !== undefined) dbData.storageBackupEnabled = body.upload.storage.backupEnabled;
+ if (body.upload.storage.compressionEnabled !== undefined) dbData.storageCompressionEnabled = body.upload.storage.compressionEnabled;
+ }
+ }
+
+ // System Settings
+ if (body.system) {
+ if (body.system.timezone !== undefined) dbData.systemTimezone = body.system.timezone;
+ if (body.system.backupSchedule !== undefined) dbData.systemBackupSchedule = body.system.backupSchedule;
+ if (body.system.logLevel !== undefined) dbData.systemLogLevel = body.system.logLevel;
+ if (body.system.maintenanceMode !== undefined) dbData.systemMaintenanceMode = body.system.maintenanceMode;
+ if (body.system.autoUpdates !== undefined) dbData.systemAutoUpdates = body.system.autoUpdates;
+ if (body.system.systemMonitoring !== undefined) dbData.systemMonitoring = body.system.systemMonitoring;
+ if (body.system.performanceMetrics !== undefined) dbData.systemPerformanceMetrics = body.system.performanceMetrics;
+ }
+
+ let settings;
+ if (existingSettings) {
+ // Update existing settings
+ settings = await prisma.dms_settings.update({
+ where: { settingID: existingSettings.settingID },
+ data: dbData,
+ });
+ } else {
+ // Create new settings
+ settings = await prisma.dms_settings.create({
+ data: {
+ ...dbData,
+ settingCreatedDate: new Date(),
+ },
+ });
+ }
+
+ return {
+ statusCode: 200,
+ message: "DMS settings updated successfully",
+ data: { settingID: settings.settingID },
+ };
+ }
+
+ return {
+ statusCode: 405,
+ message: "Method not allowed",
+ };
+ } catch (error) {
+ console.error("DMS settings API error:", error);
+
+ // Provide more specific error messages
+ if (error.code === 'P2002') {
+ return {
+ statusCode: 400,
+ message: "Duplicate entry error",
+ error: error.message,
+ };
+ }
+
+ if (error.code === 'P2025') {
+ return {
+ statusCode: 404,
+ message: "Record not found",
+ error: error.message,
+ };
+ }
+
+ if (error.code && error.code.startsWith('P')) {
+ return {
+ statusCode: 400,
+ message: "Database error",
+ error: error.message,
+ code: error.code,
+ };
+ }
+
+ return {
+ statusCode: 500,
+ message: "Internal server error",
+ error: error.message,
+ };
+ } finally {
+ await prisma.$disconnect();
+ }
+});
\ No newline at end of file
diff --git a/stores/dms.js b/stores/dms.js
index 51e30d1..611b7e5 100644
--- a/stores/dms.js
+++ b/stores/dms.js
@@ -12,10 +12,32 @@ export const useDmsStore = defineStore('dms', {
id: 'user1',
name: 'Aiman Fakhrullah',
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'
},
+ // 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
cabinetAccessTypes: [
{ id: 'public', name: 'Public Access', icon: 'check-circle', color: 'green' },
@@ -30,6 +52,224 @@ export const useDmsStore = defineStore('dms', {
{ id: 'rejected', name: 'Rejected', color: 'red' }
],
+ // Access requests tracking
+ accessRequests: [
+ {
+ id: 'req1',
+ documentId: 'private-file1',
+ documentName: 'Annual_Budget_2024_CONFIDENTIAL.xlsx',
+ documentPath: '/private-cabinet1/private-drawer1/Annual_Budget_2024_CONFIDENTIAL.xlsx',
+ requesterId: 'user1',
+ requesterName: 'Aiman Fakhrullah',
+ requesterEmail: 'aiman@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: 'user2',
+ approverName: 'Ahmad Zaki',
+ approverDepartment: 'JKR Bahagian Kewangan',
+ requestDate: '2023-12-01T09:30:00Z',
+ targetResolutionTime: '2023-12-03T09:30:00Z', // 48 hours SLA
+ responseDate: '2023-12-02T14:15:00Z',
+ status: 'approved',
+ accessType: 'view',
+ accessDuration: '7 days',
+ justification: 'Need to review budget allocations for the upcoming bridge construction project',
+ notes: 'Approved for view-only access. Please ensure confidentiality is maintained.'
+ },
+ {
+ id: 'req2',
+ documentId: 'private-drawer1',
+ documentName: 'Budget & Financial Reports',
+ documentPath: '/private-cabinet1/Budget & Financial Reports',
+ requesterId: 'user1',
+ requesterName: 'Aiman Fakhrullah',
+ requesterEmail: 'aiman@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: '2023-12-10T11:45:00Z',
+ targetResolutionTime: '2023-12-12T11:45:00Z', // 48 hours SLA
+ responseDate: null,
+ status: 'pending',
+ accessType: 'download',
+ accessDuration: '30 days',
+ justification: 'Need to analyze financial reports for the department annual review',
+ notes: null
+ },
+ {
+ id: 'req3',
+ documentId: 'jkr-kuala-terengganu',
+ documentName: 'JKR Cawangan Kuala Terengganu, Terengganu',
+ documentPath: '/private-cabinets/JKR Cawangan Kuala Terengganu, Terengganu',
+ requesterId: 'user1',
+ requesterName: 'Aiman Fakhrullah',
+ requesterEmail: 'aiman@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: 'user3',
+ approverName: 'Siti Aminah',
+ approverDepartment: 'JKR Cawangan Kuala Terengganu',
+ requestDate: '2023-12-05T15:20:00Z',
+ targetResolutionTime: '2023-12-07T15:20:00Z', // 48 hours SLA
+ responseDate: '2023-12-08T10:05:00Z', // Overdue response
+ status: 'rejected',
+ accessType: 'full',
+ accessDuration: '90 days',
+ justification: 'Need full access for the joint infrastructure project between Kota Bharu and Kuala Terengganu branches',
+ notes: 'Request denied. Please have your department head contact our director for special authorization.'
+ },
+ {
+ id: 'req4',
+ documentId: 'jkr-batu-kawan',
+ documentName: 'JKR Cawangan Batu Kawan, Penang',
+ documentPath: '/private-cabinets/JKR Cawangan Batu Kawan, Penang',
+ requesterId: 'user1',
+ requesterName: 'Aiman Fakhrullah',
+ requesterEmail: 'aiman@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: '2023-11-28T09:15:00Z',
+ targetResolutionTime: '2023-11-30T09:15:00Z', // 48 hours SLA
+ responseDate: null,
+ status: 'pending',
+ accessType: 'view',
+ accessDuration: '14 days',
+ justification: 'Need to review similar projects in Batu Kawan for reference',
+ notes: null
+ },
+ {
+ id: 'req5',
+ documentId: 'jkr-arkitek',
+ documentName: 'JKR Cawangan Arkitek',
+ documentPath: '/private-cabinets/JKR Cawangan Arkitek',
+ requesterId: 'user1',
+ requesterName: 'Aiman Fakhrullah',
+ requesterEmail: 'aiman@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: '2023-12-15T14:30:00Z',
+ targetResolutionTime: '2023-12-17T14:30:00Z', // 48 hours SLA
+ responseDate: null,
+ status: 'pending',
+ accessType: 'view',
+ accessDuration: '30 days',
+ justification: 'Need to consult architectural plans for the new government complex',
+ notes: null
+ }
+ ],
+
+ // Department access requests (for KPI tracking)
+ departmentAccessRequests: [
+ {
+ id: 'dept-req1',
+ documentId: 'finance-reports-2023',
+ documentName: 'Finance Reports 2023',
+ documentPath: '/private-cabinet1/finance-reports-2023',
+ requesterId: 'user4',
+ requesterName: 'Ibrahim Hassan',
+ requesterEmail: 'ibrahim@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: 'user5',
+ approverName: 'Fatimah Abdullah',
+ approverDepartment: 'JKR Bahagian Kewangan',
+ requestDate: '2023-12-02T10:30:00Z',
+ targetResolutionTime: '2023-12-04T10:30:00Z',
+ responseDate: '2023-12-03T11:45:00Z',
+ status: 'approved',
+ accessType: 'view',
+ accessDuration: '14 days',
+ justification: 'Need to review financial reports for project planning',
+ notes: 'Approved with standard view-only restrictions'
+ },
+ {
+ id: 'dept-req2',
+ documentId: 'hr-policies-2023',
+ documentName: 'HR Policies 2023',
+ documentPath: '/private-cabinet2/hr-policies-2023',
+ requesterId: 'user6',
+ requesterName: 'Nurul Huda',
+ requesterEmail: 'nurul@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: 'user7',
+ approverName: 'Omar Ali',
+ approverDepartment: 'JKR Bahagian HR',
+ requestDate: '2023-12-05T09:15:00Z',
+ targetResolutionTime: '2023-12-07T09:15:00Z',
+ responseDate: '2023-12-09T16:30:00Z', // Overdue
+ status: 'approved',
+ accessType: 'download',
+ accessDuration: '30 days',
+ justification: 'Need to implement new HR policies in our department',
+ notes: 'Approved with delayed response due to verification requirements'
+ },
+ {
+ id: 'dept-req3',
+ documentId: 'strategic-plan-2024',
+ documentName: 'Strategic Plan 2024',
+ documentPath: '/private-cabinet3/strategic-plan-2024',
+ requesterId: 'user8',
+ requesterName: 'Hassan Ibrahim',
+ requesterEmail: 'hassan@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: 'user9',
+ approverName: 'Zainab Mohamed',
+ approverDepartment: 'JKR Pengarah',
+ requestDate: '2023-12-08T11:00:00Z',
+ targetResolutionTime: '2023-12-10T11:00:00Z',
+ responseDate: '2023-12-09T09:45:00Z',
+ status: 'rejected',
+ accessType: 'full',
+ accessDuration: '90 days',
+ justification: 'Need full access to implement strategic initiatives',
+ notes: 'Rejected due to insufficient clearance level. Please request through department head.'
+ },
+ {
+ id: 'dept-req4',
+ documentId: 'project-tendering-guidelines',
+ documentName: 'Project Tendering Guidelines',
+ documentPath: '/private-cabinet4/project-tendering-guidelines',
+ requesterId: 'user10',
+ requesterName: 'Razak Ismail',
+ requesterEmail: 'razak@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: '2023-12-12T14:15:00Z',
+ targetResolutionTime: '2023-12-14T14:15:00Z',
+ responseDate: null,
+ status: 'pending',
+ accessType: 'view',
+ accessDuration: '7 days',
+ justification: 'Need to review tendering guidelines for upcoming project',
+ notes: null
+ },
+ {
+ id: 'dept-req5',
+ documentId: 'audit-reports-2023',
+ documentName: 'Audit Reports 2023',
+ documentPath: '/private-cabinet5/audit-reports-2023',
+ requesterId: 'user11',
+ requesterName: 'Aminah Zainal',
+ requesterEmail: 'aminah@example.com',
+ requesterDepartment: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: '2023-12-14T10:30:00Z',
+ targetResolutionTime: '2023-12-16T10:30:00Z',
+ responseDate: null,
+ status: 'pending',
+ accessType: 'download',
+ accessDuration: '14 days',
+ justification: 'Need to review audit findings for compliance',
+ notes: null
+ }
+ ],
+
// Items state - in production this would be loaded from API
cabinets: [
{
@@ -268,75 +508,24 @@ export const useDmsStore = defineStore('dms', {
}
],
- // Access requests
- accessRequests: [
- {
- id: 'req1',
- userId: 'user1',
- userName: 'Aiman Fakhrullah',
- cabinetId: 'jkr-batu-kawan',
- cabinetName: 'JKR Cawangan Batu Kawan, Penang',
- requestDate: '2023-06-15',
- status: 'pending',
- reason: 'Need access for project collaboration'
- },
- {
- id: 'req2',
- userId: 'user1',
- userName: 'Aiman Fakhrullah',
- cabinetId: 'jkr-kuala-terengganu',
- cabinetName: 'JKR Cawangan Kuala Terengganu, Terengganu',
- requestDate: '2023-06-16',
- status: 'pending',
- reason: 'Required for cross-department coordination'
- },
- {
- id: 'req3',
- userId: 'user1',
- userName: 'Aiman Fakhrullah',
- cabinetId: 'jkr-arkitek',
- cabinetName: 'JKR Cawangan Arkitek',
- requestDate: '2023-06-17',
- status: 'pending',
- reason: 'Need architectural plans for current project'
- },
- {
- id: 'req4',
- userId: 'user1',
- userName: 'Aiman Fakhrullah',
- cabinetId: 'jkr-putrajaya',
- cabinetName: 'JKR Cawangan Putrajaya',
- requestDate: '2023-06-01',
- status: 'rejected',
- reason: 'Need access to headquarters documents',
- rejectionReason: 'Access restricted to headquarters staff only'
- }
- ],
-
- // Selected item
- selectedItem: null,
-
- // View settings
- viewMode: 'list', // list, grid, details
- sortBy: 'name',
- sortDirection: 'asc',
-
- // User permissions - would be loaded from auth service
- userPermissions: {
- canCreate: true,
- canEdit: true,
- canDelete: true,
- canManageAccess: true
- },
-
- // Flags
- isLoading: false,
- showFileViewer: false,
- viewerDocument: null,
- searchQuery: '',
+ // Search state
searchResults: [],
+ searchQuery: '',
- // Settings integration
+ // Upload state
+ uploadProgress: 0,
+ currentUploads: [],
+
+ // View state
+ viewMode: 'list', // list, grid, details
+ sortBy: 'name', // name, modified, size, type
+ sortDirection: 'asc', // asc, desc
+
+ // File viewer state
+ fileViewerOpen: false,
+ currentDocument: null,
+
+ // System settings
systemSettings: {
upload: {
allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
@@ -521,8 +710,44 @@ export const useDmsStore = defineStore('dms', {
},
// Get pending access requests
- pendingAccessRequests: (state) => {
- return state.accessRequests.filter(req => req.status === 'pending');
+ pendingAccessRequests() {
+ return this.accessRequests.filter(req => req.status === 'pending');
+ },
+
+ approvedAccessRequests() {
+ return this.accessRequests.filter(req => req.status === 'approved');
+ },
+
+ rejectedAccessRequests() {
+ return this.accessRequests.filter(req => req.status === 'rejected');
+ },
+
+ overdueAccessRequests() {
+ const now = new Date();
+ return this.accessRequests.filter(req => {
+ if (req.status !== 'pending') return false;
+
+ const deadline = new Date(req.targetResolutionTime);
+ return now > deadline;
+ });
+ },
+
+ // Getter for average response time
+ averageResponseTime() {
+ const resolvedRequests = this.accessRequests.filter(
+ req => req.status === 'approved' || req.status === 'rejected'
+ );
+
+ if (resolvedRequests.length === 0) return 0;
+
+ const totalResponseTime = resolvedRequests.reduce((total, req) => {
+ const requestDate = new Date(req.requestDate);
+ const responseDate = new Date(req.responseDate);
+ const responseTimeHours = (responseDate - requestDate) / (1000 * 60 * 60);
+ return total + responseTimeHours;
+ }, 0);
+
+ return totalResponseTime / resolvedRequests.length;
}
},
@@ -1151,46 +1376,55 @@ export const useDmsStore = defineStore('dms', {
},
// Access request functionality
- async requestAccess(itemId, accessLevel, justification, duration = '7 days') {
+ async requestAccess(itemId, accessType, justification, duration = '7 days') {
this.isLoading = true;
try {
// Mock API delay
- await new Promise(resolve => setTimeout(resolve, 500));
+ await new Promise(resolve => setTimeout(resolve, 800));
- // Generate a unique request ID
- const requestId = `req${Date.now()}`;
+ // Find the item being requested
+ const item = this.findItemById(itemId);
+
+ if (!item) {
+ throw new Error(`Item with id ${itemId} not found`);
+ }
+
+ // Calculate target resolution time (48 hours SLA)
+ const requestDate = new Date();
+ const targetResolutionTime = new Date(requestDate);
+ targetResolutionTime.setHours(targetResolutionTime.getHours() + 48);
// Create new access request
const newRequest = {
- id: requestId,
- userId: 'current-user-id', // Would come from auth store
- userName: 'Current User', // Would come from auth store
- itemId: itemId,
- accessLevel: accessLevel,
- justification: justification,
- duration: duration,
- requestDate: new Date().toISOString().split('T')[0],
- status: 'pending'
+ id: `req-${Date.now()}`,
+ documentId: item.id,
+ documentName: item.name,
+ documentPath: this.getItemPath(item),
+ requesterId: this.currentUser.id,
+ requesterName: this.currentUser.name,
+ requesterEmail: this.currentUser.email,
+ requesterDepartment: this.currentUser.department,
+ approverId: null,
+ approverName: null,
+ approverDepartment: null,
+ requestDate: requestDate.toISOString(),
+ targetResolutionTime: targetResolutionTime.toISOString(),
+ responseDate: null,
+ status: 'pending',
+ accessType,
+ accessDuration: duration,
+ justification,
+ notes: null
};
- // Add to access requests
+ // Add the request to the store
this.accessRequests.push(newRequest);
- // Update the item's access request status (for mock data)
- // In production, this would be handled server-side
- const updateItemStatus = (items, id) => {
- for (const item of items) {
- if (item.id === id) {
- item.accessRequestStatus = 'pending';
- return true;
- }
- }
- return false;
- };
-
- // Try to find and update the item in the mock data arrays
- // This is a simplified approach for demo purposes
+ // Update the item's access request status if it's a cabinet
+ if (item.type === 'cabinet' || item.type === 'cabinet-group') {
+ this.updateCabinetAccessStatus(itemId, 'pending');
+ }
return newRequest;
} catch (error) {
@@ -1199,6 +1433,447 @@ export const useDmsStore = defineStore('dms', {
} finally {
this.isLoading = false;
}
+ },
+
+ // Get access requests (can filter by user)
+ async getAccessRequests(userId = null) {
+ // Simulate API delay for realistic testing
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ if (userId) {
+ return this.accessRequests.filter(req => req.requesterId === userId);
+ }
+
+ return this.accessRequests;
+ },
+
+ // Get department access requests
+ async getDepartmentAccessRequests() {
+ // Simulate API delay for realistic testing
+ await new Promise(resolve => setTimeout(resolve, 600));
+
+ return this.departmentAccessRequests;
+ },
+
+ // Approve an access request
+ async approveAccessRequest(requestId, notes = '') {
+ // Simulate API delay for realistic testing
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ const requestIndex = this.accessRequests.findIndex(req => req.id === requestId);
+
+ if (requestIndex === -1) {
+ throw new Error(`Request with id ${requestId} not found`);
+ }
+
+ // Update the request
+ const request = this.accessRequests[requestIndex];
+ const updatedRequest = {
+ ...request,
+ approverId: this.currentUser.id,
+ approverName: this.currentUser.name,
+ approverDepartment: this.currentUser.department,
+ responseDate: new Date().toISOString(),
+ status: 'approved',
+ notes
+ };
+
+ // Update in store
+ this.accessRequests[requestIndex] = updatedRequest;
+
+ // Update the item's access status if it's a cabinet
+ if (request.documentId) {
+ const item = this.findItemById(request.documentId);
+ if (item && (item.type === 'cabinet' || item.type === 'cabinet-group')) {
+ this.updateCabinetAccessStatus(request.documentId, 'approved');
+ }
+ }
+
+ return updatedRequest;
+ },
+
+ // Reject an access request
+ async rejectAccessRequest(requestId, notes = '') {
+ // Simulate API delay for realistic testing
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ const requestIndex = this.accessRequests.findIndex(req => req.id === requestId);
+
+ if (requestIndex === -1) {
+ throw new Error(`Request with id ${requestId} not found`);
+ }
+
+ // Update the request
+ const request = this.accessRequests[requestIndex];
+ const updatedRequest = {
+ ...request,
+ approverId: this.currentUser.id,
+ approverName: this.currentUser.name,
+ approverDepartment: this.currentUser.department,
+ responseDate: new Date().toISOString(),
+ status: 'rejected',
+ notes
+ };
+
+ // Update in store
+ this.accessRequests[requestIndex] = updatedRequest;
+
+ // Update the item's access status if it's a cabinet
+ if (request.documentId) {
+ const item = this.findItemById(request.documentId);
+ if (item && (item.type === 'cabinet' || item.type === 'cabinet-group')) {
+ this.updateCabinetAccessStatus(request.documentId, 'rejected');
+ }
+ }
+
+ return updatedRequest;
+ },
+
+ // Get KPI metrics for access requests
+ async getAccessRequestMetrics(timeRange = '30days', userId = null) {
+ // Simulate API delay
+ await new Promise(resolve => setTimeout(resolve, 700));
+
+ // Filter requests by time range
+ const now = new Date();
+ let daysToLookBack = 30;
+
+ switch (timeRange) {
+ case '7days':
+ daysToLookBack = 7;
+ break;
+ case '30days':
+ daysToLookBack = 30;
+ break;
+ case '90days':
+ daysToLookBack = 90;
+ break;
+ case '365days':
+ daysToLookBack = 365;
+ break;
+ }
+
+ const cutoffDate = new Date(now);
+ cutoffDate.setDate(cutoffDate.getDate() - daysToLookBack);
+
+ let requestsToAnalyze = this.accessRequests;
+
+ // Filter by user if specified
+ if (userId) {
+ requestsToAnalyze = requestsToAnalyze.filter(req => req.requesterId === userId);
+ }
+
+ // Filter by date range
+ requestsToAnalyze = requestsToAnalyze.filter(req => {
+ const requestDate = new Date(req.requestDate);
+ return requestDate >= cutoffDate;
+ });
+
+ // Calculate metrics
+ const totalRequests = requestsToAnalyze.length;
+ const pendingRequests = requestsToAnalyze.filter(req => req.status === 'pending').length;
+ const approvedRequests = requestsToAnalyze.filter(req => req.status === 'approved').length;
+ const rejectedRequests = requestsToAnalyze.filter(req => req.status === 'rejected').length;
+
+ // Calculate response times
+ const resolvedRequests = requestsToAnalyze.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 metrics
+ const overdueRequests = requestsToAnalyze.filter(req => {
+ if (req.status !== 'pending') return false;
+
+ const deadline = new Date(req.targetResolutionTime);
+ return now > deadline;
+ }).length;
+
+ const overduePercentage = totalRequests > 0
+ ? overdueRequests / totalRequests
+ : 0;
+
+ return {
+ totalRequests,
+ pendingRequests,
+ approvedRequests,
+ rejectedRequests,
+ avgResponseTime,
+ fastestResponse: fastestResponse === Infinity ? 0 : fastestResponse,
+ slowestResponse,
+ overdueRequests,
+ overduePercentage
+ };
+ },
+
+ // Helper function to get the path for an item
+ getItemPath(item) {
+ if (!item.parentPath) return `/${item.name}`;
+ return `${item.parentPath}/${item.name}`;
+ },
+
+ // Find an item by ID
+ findItemById(id) {
+ // Look through cabinets recursively
+ const findInCabinets = (cabinets, targetId) => {
+ for (const cabinet of cabinets) {
+ if (cabinet.id === targetId) {
+ return cabinet;
+ }
+
+ if (cabinet.children) {
+ const found = findInCabinets(cabinet.children, targetId);
+ if (found) return found;
+ }
+ }
+ return null;
+ };
+
+ // First check cabinets
+ const cabinetItem = findInCabinets(this.cabinets, id);
+ if (cabinetItem) return cabinetItem;
+
+ // Then check regular items
+ return this.items.find(item => item.id === id);
+ },
+
+ // Update cabinet access status
+ updateCabinetAccessStatus(cabinetId, status) {
+ const updateCabinet = (cabinets, id, newStatus) => {
+ return cabinets.map(cabinet => {
+ if (cabinet.id === id) {
+ return {
+ ...cabinet,
+ accessRequestStatus: newStatus,
+ hasAccess: newStatus === 'approved',
+ isLocked: newStatus === 'rejected'
+ };
+ }
+
+ if (cabinet.children) {
+ return {
+ ...cabinet,
+ children: updateCabinet(cabinet.children, id, newStatus)
+ };
+ }
+
+ return cabinet;
+ });
+ };
+
+ this.cabinets = updateCabinet(this.cabinets, cabinetId, status);
+ },
+
+ // Authentik integration placeholder - this would be replaced with actual Authentik API calls
+ async authenticateWithAuthentik(username, password) {
+ // Simulate API delay
+ await new Promise(resolve => setTimeout(resolve, 800));
+
+ // This is a placeholder for the actual Authentik integration
+ // 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') {
+ return {
+ user: {
+ id: 'admin1',
+ name: 'Admin User',
+ email: 'admin@example.com',
+ role: 'admin',
+ department: 'IT Department'
+ },
+ token: 'sample-authentik-token'
+ };
+ }
+
+ if (username === 'user' && password === 'password') {
+ return {
+ user: {
+ id: 'user1',
+ name: 'Aiman Fakhrullah',
+ email: 'aiman@example.com',
+ role: 'user',
+ department: 'General Department'
+ },
+ token: 'sample-authentik-token'
+ };
+ }
+
+ throw new Error('Authentication failed');
+ },
+
+ // Get RBAC permissions from Authentik
+ async getRbacPermissions(userId) {
+ // Simulate API delay
+ await new Promise(resolve => setTimeout(resolve, 600));
+
+ // This is a placeholder for the actual Authentik integration
+ // In a real implementation, this would fetch RBAC permissions from Authentik
+
+ 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': {
+ roles: ['admin'],
+ 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: false
+ },
+ users: {
+ manage: false
+ },
+ roles: {
+ manage: false
+ }
+ }
+ },
+ 'user1': {
+ roles: ['user'],
+ permissions: {
+ documents: {
+ view: true,
+ edit: true,
+ delete: false,
+ approve: false,
+ reject: false,
+ download: true
+ },
+ cabinets: {
+ view: true,
+ create: false,
+ edit: false,
+ delete: false
+ },
+ accessRequests: {
+ approve: false,
+ reject: false,
+ viewAll: false
+ },
+ systemSettings: {
+ manage: false
+ },
+ users: {
+ manage: false
+ },
+ roles: {
+ manage: false
+ }
+ }
+ }
+ };
+
+ return permissions[userId] || {
+ roles: [],
+ permissions: {
+ documents: {
+ view: false,
+ edit: false,
+ delete: false,
+ approve: false,
+ reject: false,
+ download: false
+ },
+ cabinets: {
+ view: false,
+ create: false,
+ edit: false,
+ delete: false
+ },
+ accessRequests: {
+ approve: false,
+ reject: false,
+ viewAll: false
+ },
+ systemSettings: {
+ manage: false
+ },
+ users: {
+ manage: false
+ },
+ roles: {
+ manage: false
+ }
+ }
+ };
}
}
});
\ No newline at end of file