EDMS/stores/dms.js
2025-05-30 21:08:11 +08:00

1204 lines
36 KiB
JavaScript

import { defineStore } from 'pinia';
export const useDmsStore = defineStore('dms', {
state: () => ({
// Navigation state
currentPath: '/',
pathHistory: ['/'],
historyIndex: 0,
// User information
currentUser: {
id: 'user1',
name: 'Aiman Fakhrullah',
email: 'aiman@example.com',
role: 'engineer', // engineer, admin, manager, etc.
department: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu'
},
// Cabinet access types
cabinetAccessTypes: [
{ id: 'public', name: 'Public Access', icon: 'check-circle', color: 'green' },
{ id: 'personal', name: 'Personal Access', icon: 'circle-check', color: 'orange' },
{ id: 'private', name: 'Private Access', icon: 'lock', color: 'red' }
],
// Access request statuses
accessRequestStatuses: [
{ id: 'pending', name: 'Pending', color: 'orange' },
{ id: 'approved', name: 'Approved', color: 'green' },
{ id: 'rejected', name: 'Rejected', color: 'red' }
],
// Items state - in production this would be loaded from API
cabinets: [
{
id: 'public-cabinet',
name: 'Public Cabinet',
type: 'cabinet',
accessType: 'public',
children: [
{
id: 'public-cabinet-1',
name: 'Public Cabinet',
type: 'cabinet',
accessType: 'public',
parentId: 'public-cabinet'
}
]
},
{
id: 'my-cabinets',
name: 'My Cabinets',
type: 'cabinet-group',
accessType: 'personal',
children: [
{
id: 'jkr-tebedu',
name: 'JKR Cawangan Tebedu, Sarawak',
type: 'cabinet',
accessType: 'personal',
parentId: 'my-cabinets',
hasAccess: true
},
{
id: 'jkr-kota-bharu',
name: 'JKR Cawangan Kota Bharu, Kelantan',
type: 'cabinet',
accessType: 'personal',
parentId: 'my-cabinets',
hasAccess: true,
children: [
{
id: 'jkr-kewangan-kb',
name: 'JKR Bahagian Kewangan Cawangan Kota Bharu',
type: 'cabinet',
accessType: 'personal',
parentId: 'jkr-kota-bharu',
hasAccess: true
},
{
id: 'jkr-kejuruteraan-kb',
name: 'JKR Bahagian Kejuruteraan Awam Cawangan Kota Bharu',
type: 'cabinet',
accessType: 'personal',
parentId: 'jkr-kota-bharu',
hasAccess: true,
children: [
{
id: 'pembinaan-jambatan-kb',
name: 'Pembinaan Jambatan Kota Bharu',
type: 'cabinet',
accessType: 'personal',
parentId: 'jkr-kejuruteraan-kb',
hasAccess: true,
children: [
{
id: 'kewangan-tag',
name: 'Kewangan',
type: 'tag',
color: 'purple',
parentId: 'pembinaan-jambatan-kb'
},
{
id: 'kejuruteraan-awam-tag',
name: 'Kejuruteraan Awam',
type: 'tag',
color: 'purple',
parentId: 'pembinaan-jambatan-kb'
},
{
id: 'teknologi-maklumat-tag',
name: 'Teknologi Maklumat',
type: 'tag',
color: 'purple',
parentId: 'pembinaan-jambatan-kb'
}
]
},
{
id: 'projek-jalan-raya-kb',
name: 'Projek Jalan Raya Kota Bharu',
type: 'cabinet',
accessType: 'personal',
parentId: 'jkr-kejuruteraan-kb',
hasAccess: true
}
]
},
{
id: 'jkr-teknologi-kb',
name: 'JKR Bahagian Teknologi Maklumat Cawangan Kota Bharu',
type: 'cabinet',
accessType: 'personal',
parentId: 'jkr-kota-bharu',
hasAccess: true
}
]
},
{
id: 'jkr-ipoh',
name: 'JKR Cawangan Ipoh, Perak',
type: 'cabinet',
accessType: 'personal',
parentId: 'my-cabinets',
hasAccess: true
}
]
},
{
id: 'private-cabinets',
name: 'Private Cabinets',
type: 'cabinet-group',
accessType: 'private',
children: [
{
id: 'jkr-batu-kawan',
name: 'JKR Cawangan Batu Kawan, Penang',
type: 'cabinet',
accessType: 'private',
parentId: 'private-cabinets',
hasAccess: false,
accessRequestStatus: 'pending'
},
{
id: 'jkr-kuala-terengganu',
name: 'JKR Cawangan Kuala Terengganu, Terengganu',
type: 'cabinet',
accessType: 'private',
parentId: 'private-cabinets',
hasAccess: false,
accessRequestStatus: 'pending'
},
{
id: 'jkr-arkitek',
name: 'JKR Cawangan Arkitek',
type: 'cabinet',
accessType: 'private',
parentId: 'private-cabinets',
hasAccess: false,
accessRequestStatus: 'pending'
},
{
id: 'jkr-putrajaya',
name: 'JKR Cawangan Putrajaya',
type: 'cabinet',
accessType: 'private',
parentId: 'private-cabinets',
hasAccess: false,
accessRequestStatus: 'rejected',
isLocked: true
}
]
}
],
// Files within cabinets
items: [
{
id: 'file1',
name: 'Pembangunan_Sistem_IT_2021.pdf',
type: 'file',
extension: 'pdf',
size: '4MB',
modified: '2021-05-20',
cabinetId: 'jkr-kota-bharu',
accessType: 'personal',
status: 'locked',
info: {
title: 'Projek Pembangunan Sistem IT',
subject: 'Dokumen spesifikasi sistem',
state: 'Kelantan',
date: '2021-05-20',
user: 'Mohd Faizal bin Abdullah',
storeDate: '2021-05-25'
}
},
{
id: 'file2',
name: 'Projek_Jalan_Raya_Kota_Bharu.pdf',
type: 'file',
extension: 'pdf',
size: '5MB',
modified: '2021-06-15',
cabinetId: 'jkr-kejuruteraan-kb',
accessType: 'personal',
status: 'unlocked'
},
{
id: 'file3',
name: 'Anggaran_Kos_Projek_MRT3.xlsx',
type: 'file',
extension: 'xlsx',
size: '3MB',
modified: '2021-07-10',
cabinetId: 'pembinaan-jambatan-kb',
accessType: 'personal',
status: 'locked'
},
{
id: 'file4',
name: 'EIA_Empangan_Nenggiri.pdf',
type: 'file',
extension: 'pdf',
size: '15MB',
modified: '2021-04-18',
cabinetId: 'jkr-ipoh',
accessType: 'personal',
status: 'locked'
},
{
id: 'file5',
name: 'Borang_Maklumabalas.xlsx',
type: 'file',
extension: 'xlsx',
size: '34.1 KB',
modified: '2025-01-27',
cabinetId: 'public-cabinet-1',
accessType: 'public',
createdBy: 'aimantasan',
status: 'unlocked',
info: {
authors: 'aimantasan',
lastSavedBy: 'aimantasan',
dateAccessed: '2025-05-28',
dateModified: '2025-01-27',
contentCreated: '2025-01-27'
}
}
],
// 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: '',
searchResults: [],
// Settings integration
systemSettings: {
upload: {
allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
blockedFileTypes: ['exe', 'bat', 'cmd'],
maxFileSize: 100 * 1024 * 1024, // 100MB in bytes
quotas: {
perUser: 5000 * 1024 * 1024, // 5GB
perGroup: 50000 * 1024 * 1024, // 50GB
perProject: 100000 * 1024 * 1024 // 100GB
}
},
metadata: {
customFields: [
{ name: 'Department', type: 'dropdown', required: true, options: ['HR', 'Finance', 'IT', 'Legal'] },
{ name: 'Priority', type: 'select', required: false, options: ['Low', 'Medium', 'High', 'Critical'] },
{ name: 'Project Code', type: 'text', required: true },
{ name: 'Review Date', type: 'date', required: false }
],
predefinedTags: ['urgent', 'confidential', 'public', 'draft', 'final'],
userGeneratedTags: true,
tagSuggestions: true
},
documents: {
versionControl: {
enabled: true,
maxVersions: 10,
autoVersioning: true
},
namingConventions: {
autoGenerate: true,
pattern: '{department}_{title}_{date}'
}
},
access: {
rbacEnabled: true,
permissions: {
view: true,
edit: true,
delete: false,
download: true,
share: true
}
}
},
// Enhanced user permissions with detailed tracking
userQuotas: {
used: 1500 * 1024 * 1024, // 1.5GB used
limit: 5000 * 1024 * 1024, // 5GB limit
documents: 450,
maxDocuments: 1000
},
// Document metadata templates
metadataTemplates: {
standard: {
title: '',
description: '',
department: '',
author: '',
tags: [],
priority: 'Medium',
reviewDate: null,
projectCode: ''
},
contract: {
title: '',
description: '',
department: 'Legal',
contractType: '',
vendor: '',
startDate: null,
endDate: null,
value: '',
tags: ['contract'],
priority: 'High'
},
report: {
title: '',
description: '',
department: '',
reportType: '',
period: '',
author: '',
tags: ['report'],
priority: 'Medium'
}
},
// Version tracking
documentVersions: {},
// Enhanced search with metadata
advancedSearchFilters: {
fileType: [],
department: [],
tags: [],
dateRange: { start: null, end: null },
author: [],
priority: [],
hasVersions: false
}
}),
getters: {
// Get items at the current cabinet
currentCabinetItems: (state) => {
const currentCabinetId = state.currentPath.split('/').filter(Boolean).pop();
return state.items.filter(item => item.cabinetId === currentCabinetId);
},
// Get breadcrumbs for current path
breadcrumbs: (state) => {
if (state.currentPath === '/') return [{ name: 'Home', path: '/' }];
const paths = state.currentPath.split('/').filter(Boolean);
let breadcrumbPath = '';
return [
{ name: 'Home', path: '/' },
...paths.map(segment => {
breadcrumbPath += `/${segment}`;
// Find the actual cabinet name
const findCabinetName = (cabinets, id) => {
for (const cabinet of cabinets) {
if (cabinet.id === id) return cabinet.name;
if (cabinet.children) {
const name = findCabinetName(cabinet.children, id);
if (name) return name;
}
}
return null;
};
const name = findCabinetName(state.cabinets, segment) || segment;
return {
name: name,
path: breadcrumbPath
};
})
];
},
// Get public cabinets
publicCabinets: (state) => {
return state.cabinets.filter(cabinet => cabinet.accessType === 'public');
},
// Get personal cabinets (ones the user has access to)
personalCabinets: (state) => {
return state.cabinets.find(cabinet => cabinet.id === 'my-cabinets') || null;
},
// Get private cabinets
privateCabinets: (state) => {
return state.cabinets.find(cabinet => cabinet.id === 'private-cabinets') || null;
},
// Check if we can navigate back
canGoBack: (state) => {
return state.historyIndex > 0;
},
// Check if we can navigate forward
canGoForward: (state) => {
return state.historyIndex < state.pathHistory.length - 1;
},
// Filter items by access level
publicDocuments: (state) => {
return state.items.filter(item => item.accessType === 'public');
},
personalDocuments: (state) => {
return state.items.filter(item => item.accessType === 'personal');
},
privateDocuments: (state) => {
return state.items.filter(item => item.accessType === 'private');
},
// Get pending access requests
pendingAccessRequests: (state) => {
return state.accessRequests.filter(req => req.status === 'pending');
}
},
actions: {
// Navigation actions
navigateTo(path) {
// Add to history if it's a new path
if (path !== this.currentPath) {
// If we navigated back and then to a new path, truncate the forward history
if (this.historyIndex < this.pathHistory.length - 1) {
this.pathHistory = this.pathHistory.slice(0, this.historyIndex + 1);
}
this.pathHistory.push(path);
this.historyIndex = this.pathHistory.length - 1;
}
this.currentPath = path;
this.selectedItem = null;
},
navigateBack() {
if (this.canGoBack) {
this.historyIndex--;
this.currentPath = this.pathHistory[this.historyIndex];
this.selectedItem = null;
}
},
navigateForward() {
if (this.canGoForward) {
this.historyIndex++;
this.currentPath = this.pathHistory[this.historyIndex];
this.selectedItem = null;
}
},
selectItem(item) {
this.selectedItem = item;
},
// View actions
setViewMode(mode) {
this.viewMode = mode;
},
setSortBy(field) {
if (this.sortBy === field) {
// Toggle direction if clicking the same field
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortBy = field;
this.sortDirection = 'asc';
}
},
// File viewer actions
openFileViewer(document) {
this.viewerDocument = document;
this.showFileViewer = true;
},
closeFileViewer() {
this.showFileViewer = false;
this.viewerDocument = null;
},
// Enhanced file validation based on settings
validateFile(file) {
const errors = [];
const settings = this.systemSettings.upload;
// Check file type
const fileExtension = file.name.split('.').pop().toLowerCase();
if (!settings.allowedFileTypes.includes(fileExtension)) {
errors.push(`File type .${fileExtension} is not allowed`);
}
if (settings.blockedFileTypes.includes(fileExtension)) {
errors.push(`File type .${fileExtension} is blocked`);
}
// Check file size
if (file.size > settings.maxFileSize) {
const maxSizeMB = settings.maxFileSize / (1024 * 1024);
errors.push(`File size exceeds limit of ${maxSizeMB}MB`);
}
// Check user quota
if (this.userQuotas.used + file.size > this.userQuotas.limit) {
errors.push('Upload would exceed your storage quota');
}
return {
isValid: errors.length === 0,
errors
};
},
// Enhanced metadata validation
validateMetadata(metadata, template = 'standard') {
const errors = [];
const templateConfig = this.metadataTemplates[template];
const customFields = this.systemSettings.metadata.customFields;
// Validate required custom fields
customFields.forEach(field => {
if (field.required && (!metadata[field.name] || metadata[field.name].toString().trim() === '')) {
errors.push(`${field.name} is required`);
}
// Validate field options for dropdown/select types
if (field.options && metadata[field.name] && !field.options.includes(metadata[field.name])) {
errors.push(`Invalid value for ${field.name}`);
}
});
// Validate document naming convention
if (this.systemSettings.documents.namingConventions.autoGenerate) {
const pattern = this.systemSettings.documents.namingConventions.pattern;
const requiredFields = pattern.match(/{(\w+)}/g)?.map(match => match.slice(1, -1)) || [];
requiredFields.forEach(field => {
if (!metadata[field]) {
errors.push(`${field} is required for auto-generated naming`);
}
});
}
return {
isValid: errors.length === 0,
errors
};
},
// Generate document name based on naming convention
generateDocumentName(metadata, originalFileName) {
if (!this.systemSettings.documents.namingConventions.autoGenerate) {
return originalFileName;
}
let pattern = this.systemSettings.documents.namingConventions.pattern;
const date = new Date().toISOString().split('T')[0];
// Replace placeholders
pattern = pattern.replace(/{department}/g, metadata.department || 'unknown');
pattern = pattern.replace(/{title}/g, metadata.title || 'document');
pattern = pattern.replace(/{date}/g, date);
pattern = pattern.replace(/{author}/g, metadata.author || 'unknown');
pattern = pattern.replace(/{projectCode}/g, metadata.projectCode || '');
// Get file extension
const extension = originalFileName.split('.').pop();
return `${pattern}.${extension}`;
},
// Enhanced upload with metadata and validation
async uploadFileWithMetadata(file, metadata, currentPath) {
// Validate file
const fileValidation = this.validateFile(file);
if (!fileValidation.isValid) {
throw new Error(`File validation failed: ${fileValidation.errors.join(', ')}`);
}
// Validate metadata
const metadataValidation = this.validateMetadata(metadata);
if (!metadataValidation.isValid) {
throw new Error(`Metadata validation failed: ${metadataValidation.errors.join(', ')}`);
}
this.isLoading = true;
try {
// Generate document name
const documentName = this.generateDocumentName(metadata, file.name);
// Create document object
const newDocument = {
id: `doc_${Date.now()}`,
name: documentName,
originalName: file.name,
type: 'file',
extension: file.name.split('.').pop().toLowerCase(),
size: this.formatFileSize(file.size),
modified: new Date().toLocaleDateString(),
author: metadata.author || 'Current User',
department: metadata.department,
description: metadata.description,
tags: metadata.tags || [],
priority: metadata.priority,
projectCode: metadata.projectCode,
reviewDate: metadata.reviewDate,
version: 1,
hasAccess: true,
accessType: metadata.accessType || 'private',
metadata: { ...metadata }
};
// Add to items (simulate upload)
this.items.push(newDocument);
// Update user quota
this.userQuotas.used += file.size;
this.userQuotas.documents += 1;
// Initialize version tracking if enabled
if (this.systemSettings.documents.versionControl.enabled) {
this.documentVersions[newDocument.id] = [{
version: 1,
uploadDate: new Date().toISOString(),
author: metadata.author,
changes: 'Initial upload',
fileSize: file.size
}];
}
return newDocument;
} catch (error) {
console.error('Upload failed:', error);
throw error;
} finally {
this.isLoading = false;
}
},
// Version control actions
async createNewVersion(documentId, file, metadata) {
if (!this.systemSettings.documents.versionControl.enabled) {
throw new Error('Version control is not enabled');
}
const document = this.items.find(item => item.id === documentId);
if (!document) {
throw new Error('Document not found');
}
// Check version limit
const versions = this.documentVersions[documentId] || [];
if (versions.length >= this.systemSettings.documents.versionControl.maxVersions) {
// Remove oldest version
versions.shift();
}
// Create new version
const newVersion = {
version: document.version + 1,
uploadDate: new Date().toISOString(),
author: metadata.author,
changes: metadata.changes || 'Updated document',
fileSize: file.size
};
// Update document
document.version = newVersion.version;
document.modified = new Date().toLocaleDateString();
document.size = this.formatFileSize(file.size);
// Add version to tracking
versions.push(newVersion);
this.documentVersions[documentId] = versions;
return newVersion;
},
// Get document versions
getDocumentVersions(documentId) {
return this.documentVersions[documentId] || [];
},
// Enhanced search with metadata
async advancedSearch(query, filters = {}) {
this.isLoading = true;
this.searchQuery = query;
Object.assign(this.advancedSearchFilters, filters);
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 300));
let results = this.items.filter(item => item.type === 'file');
// Text search
if (query) {
const searchLower = query.toLowerCase();
results = results.filter(item =>
item.name.toLowerCase().includes(searchLower) ||
item.description?.toLowerCase().includes(searchLower) ||
item.department?.toLowerCase().includes(searchLower) ||
item.author?.toLowerCase().includes(searchLower) ||
item.projectCode?.toLowerCase().includes(searchLower) ||
item.tags?.some(tag => tag.toLowerCase().includes(searchLower))
);
}
// Filter by file type
if (filters.fileType && filters.fileType.length > 0) {
results = results.filter(item => filters.fileType.includes(item.extension));
}
// Filter by department
if (filters.department && filters.department.length > 0) {
results = results.filter(item => filters.department.includes(item.department));
}
// Filter by tags
if (filters.tags && filters.tags.length > 0) {
results = results.filter(item =>
item.tags?.some(tag => filters.tags.includes(tag))
);
}
// Filter by priority
if (filters.priority && filters.priority.length > 0) {
results = results.filter(item => filters.priority.includes(item.priority));
}
// Filter by date range
if (filters.dateRange?.start || filters.dateRange?.end) {
results = results.filter(item => {
const itemDate = new Date(item.modified);
const startDate = filters.dateRange.start ? new Date(filters.dateRange.start) : null;
const endDate = filters.dateRange.end ? new Date(filters.dateRange.end) : null;
if (startDate && itemDate < startDate) return false;
if (endDate && itemDate > endDate) return false;
return true;
});
}
// Filter by version existence
if (filters.hasVersions) {
results = results.filter(item => this.documentVersions[item.id]?.length > 1);
}
this.searchResults = results;
return results;
} catch (error) {
console.error('Advanced search failed:', error);
throw error;
} finally {
this.isLoading = false;
}
},
// Tag suggestions
getTagSuggestions(input) {
if (!this.systemSettings.metadata.tagSuggestions) return [];
const allTags = [
...this.systemSettings.metadata.predefinedTags,
...new Set(this.items.flatMap(item => item.tags || []))
];
return allTags.filter(tag =>
tag.toLowerCase().includes(input.toLowerCase())
).slice(0, 10);
},
// Update system settings
updateSystemSettings(category, settings) {
if (this.systemSettings[category]) {
Object.assign(this.systemSettings[category], settings);
}
},
// Format file size helper
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
// Cabinet access actions
async requestCabinetAccess(cabinetId, reason) {
this.isLoading = true;
try {
// Find the cabinet
let cabinetName = '';
const findCabinet = (cabinets, id) => {
for (const cabinet of cabinets) {
if (cabinet.id === id) {
cabinetName = cabinet.name;
return cabinet;
}
if (cabinet.children) {
const result = findCabinet(cabinet.children, id);
if (result) return result;
}
}
return null;
};
const cabinet = findCabinet(this.cabinets, cabinetId);
if (!cabinet) throw new Error('Cabinet not found');
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Create new access request
const newRequest = {
id: `req${Date.now()}`,
userId: this.currentUser.id,
userName: this.currentUser.name,
cabinetId: cabinetId,
cabinetName: cabinetName,
requestDate: new Date().toISOString().split('T')[0],
status: 'pending',
reason: reason
};
// Add to access requests
this.accessRequests.push(newRequest);
// Update cabinet request status
cabinet.accessRequestStatus = 'pending';
return newRequest;
} catch (error) {
console.error('Failed to request access:', error);
throw error;
} finally {
this.isLoading = false;
}
},
async approveAccessRequest(requestId) {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Find the request
const request = this.accessRequests.find(req => req.id === requestId);
if (!request) throw new Error('Request not found');
// Update request status
request.status = 'approved';
// Find the cabinet and update access
const updateCabinetAccess = (cabinets, id) => {
for (const cabinet of cabinets) {
if (cabinet.id === id) {
cabinet.hasAccess = true;
cabinet.accessRequestStatus = 'approved';
return true;
}
if (cabinet.children) {
if (updateCabinetAccess(cabinet.children, id)) return true;
}
}
return false;
};
updateCabinetAccess(this.cabinets, request.cabinetId);
return request;
} catch (error) {
console.error('Failed to approve access request:', error);
throw error;
} finally {
this.isLoading = false;
}
},
async rejectAccessRequest(requestId, reason) {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Find the request
const request = this.accessRequests.find(req => req.id === requestId);
if (!request) throw new Error('Request not found');
// Update request status
request.status = 'rejected';
request.rejectionReason = reason;
// Find the cabinet and update access
const updateCabinetAccess = (cabinets, id) => {
for (const cabinet of cabinets) {
if (cabinet.id === id) {
cabinet.hasAccess = false;
cabinet.accessRequestStatus = 'rejected';
return true;
}
if (cabinet.children) {
if (updateCabinetAccess(cabinet.children, id)) return true;
}
}
return false;
};
updateCabinetAccess(this.cabinets, request.cabinetId);
return request;
} catch (error) {
console.error('Failed to reject access request:', error);
throw error;
} finally {
this.isLoading = false;
}
},
// CRUD operations
async createItem(item) {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Get current cabinet id
const currentCabinetId = this.currentPath.split('/').filter(Boolean).pop();
// Generate a unique ID
const newItem = {
...item,
id: `${item.type}${Date.now()}`,
cabinetId: currentCabinetId,
modified: new Date().toISOString().split('T')[0],
createdBy: this.currentUser.name
};
// Add to items
this.items.push(newItem);
return newItem;
} catch (error) {
console.error('Failed to create item:', error);
throw error;
} finally {
this.isLoading = false;
}
},
async updateItem(id, updates) {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Find the item
const itemIndex = this.items.findIndex(item => item.id === id);
if (itemIndex === -1) throw new Error('Item not found');
// Update the item
const updatedItem = {
...this.items[itemIndex],
...updates,
modified: new Date().toISOString().split('T')[0]
};
this.items.splice(itemIndex, 1, updatedItem);
return updatedItem;
} catch (error) {
console.error('Failed to update item:', error);
throw error;
} finally {
this.isLoading = false;
}
},
async deleteItem(id) {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Find the item
const itemIndex = this.items.findIndex(item => item.id === id);
if (itemIndex === -1) throw new Error('Item not found');
// Remove the item
const deletedItem = this.items.splice(itemIndex, 1)[0];
return deletedItem;
} catch (error) {
console.error('Failed to delete item:', error);
throw error;
} finally {
this.isLoading = false;
}
},
// Search functionality
async searchDocuments(query) {
this.isLoading = true;
this.searchQuery = query;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Simple search implementation - in production would be more sophisticated
const results = this.items.filter(item =>
item.type === 'file' &&
(item.name.toLowerCase().includes(query.toLowerCase()) ||
(item.info?.title && item.info.title.toLowerCase().includes(query.toLowerCase())) ||
(item.info?.subject && item.info.subject.toLowerCase().includes(query.toLowerCase())))
);
this.searchResults = results;
return results;
} catch (error) {
console.error('Failed to search documents:', error);
throw error;
} finally {
this.isLoading = false;
}
},
clearSearch() {
this.searchQuery = '';
this.searchResults = [];
},
// Access request functionality
async requestAccess(itemId, accessLevel, justification, duration = '7 days') {
this.isLoading = true;
try {
// Mock API delay
await new Promise(resolve => setTimeout(resolve, 500));
// Generate a unique request ID
const requestId = `req${Date.now()}`;
// 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'
};
// Add to access requests
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
return newRequest;
} catch (error) {
console.error('Failed to submit access request:', error);
throw error;
} finally {
this.isLoading = false;
}
}
}
});