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: [] }), 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; }, // 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; } } } });