189 lines
5.2 KiB
JavaScript
189 lines
5.2 KiB
JavaScript
/**
|
|
* IndexedDB setup using Dexie.js for workorder offline mode
|
|
*
|
|
* Schema mirrors the backend models:
|
|
* - WorkorderModel
|
|
* - WorkorderDocumentationModel
|
|
* - WorkorderJournalModel
|
|
* - WorkorderTenantConfigModel
|
|
*/
|
|
|
|
import Dexie from 'https://cdn.jsdelivr.net/npm/dexie@4/dist/dexie.min.mjs';
|
|
|
|
// Database instance
|
|
export const db = new Dexie('xinon-workorder-offline');
|
|
|
|
// Schema definition
|
|
db.version(1).stores({
|
|
// Workorder list and basic data
|
|
// Mirrors WorkorderModel from backend
|
|
workorders: 'id, companyId, status, [companyId+status], _syncStatus, _lastModified',
|
|
|
|
// Full workorder details (lazy-loaded)
|
|
// Contains customer info, address, etc.
|
|
workorderDetails: 'workorderId, lastFetched',
|
|
|
|
// Documentation metadata with thumbnail references
|
|
// Mirrors WorkorderDocumentationModel
|
|
documentation: 'id, workorderId, documentType, _syncStatus',
|
|
|
|
// Documentation thumbnails (blob storage)
|
|
thumbnails: 'documentationId, workorderId',
|
|
|
|
// Journal entries (audit trail)
|
|
// Mirrors WorkorderJournalModel
|
|
journals: 'id, workorderId, create, _localId',
|
|
|
|
// Tenant configurations
|
|
// Mirrors WorkorderTenantConfigModel
|
|
tenantConfigs: 'addressId, lastFetched',
|
|
|
|
// Sync queue for pending mutations
|
|
syncQueue: '++localId, operation, workorderId, status, createdAt',
|
|
|
|
// Pending file uploads
|
|
pendingFiles: '++localId, workorderId, documentType, status',
|
|
|
|
// Sync metadata (lastSync, etc.)
|
|
syncMeta: 'key'
|
|
});
|
|
|
|
// Sync status enum
|
|
export const SyncStatus = {
|
|
SYNCED: 'synced',
|
|
PENDING: 'pending',
|
|
CONFLICT: 'conflict',
|
|
ERROR: 'error'
|
|
};
|
|
|
|
// Operation types for sync queue
|
|
export const OperationType = {
|
|
ADD_JOURNAL: 'addJournal',
|
|
UPDATE_NOTES: 'updateNotes',
|
|
SCHEDULE_APPOINTMENT: 'scheduleAppointment',
|
|
REQUEST_INTERVENTION: 'requestIntervention',
|
|
UPDATE_CABLE_DATA: 'updateCableData',
|
|
UPLOAD_DOCUMENTATION: 'uploadDocumentation',
|
|
COMPLETE_WORKORDER: 'completeWorkorder'
|
|
};
|
|
|
|
// Operation priority (lower = higher priority)
|
|
export const OperationPriority = {
|
|
[OperationType.SCHEDULE_APPOINTMENT]: 1,
|
|
[OperationType.UPDATE_NOTES]: 2,
|
|
[OperationType.ADD_JOURNAL]: 3,
|
|
[OperationType.UPDATE_CABLE_DATA]: 4,
|
|
[OperationType.UPLOAD_DOCUMENTATION]: 5,
|
|
[OperationType.REQUEST_INTERVENTION]: 6,
|
|
[OperationType.COMPLETE_WORKORDER]: 7 // Must be last
|
|
};
|
|
|
|
/**
|
|
* Initialize database and return instance
|
|
*/
|
|
export async function initDatabase() {
|
|
try {
|
|
await db.open();
|
|
console.log('[DB] Database opened successfully');
|
|
return db;
|
|
} catch (error) {
|
|
console.error('[DB] Failed to open database:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all offline data (for logout or cache clear)
|
|
*/
|
|
export async function clearAllData() {
|
|
try {
|
|
await db.transaction('rw',
|
|
db.workorders,
|
|
db.workorderDetails,
|
|
db.documentation,
|
|
db.thumbnails,
|
|
db.journals,
|
|
db.tenantConfigs,
|
|
db.syncQueue,
|
|
db.pendingFiles,
|
|
db.syncMeta,
|
|
async () => {
|
|
await db.workorders.clear();
|
|
await db.workorderDetails.clear();
|
|
await db.documentation.clear();
|
|
await db.thumbnails.clear();
|
|
await db.journals.clear();
|
|
await db.tenantConfigs.clear();
|
|
await db.syncQueue.clear();
|
|
await db.pendingFiles.clear();
|
|
await db.syncMeta.clear();
|
|
}
|
|
);
|
|
console.log('[DB] All offline data cleared');
|
|
} catch (error) {
|
|
console.error('[DB] Failed to clear data:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get sync metadata value
|
|
*/
|
|
export async function getSyncMeta(key) {
|
|
const meta = await db.syncMeta.get(key);
|
|
return meta?.value ?? null;
|
|
}
|
|
|
|
/**
|
|
* Set sync metadata value
|
|
*/
|
|
export async function setSyncMeta(key, value) {
|
|
await db.syncMeta.put({ key, value });
|
|
}
|
|
|
|
/**
|
|
* Get pending operations count
|
|
*/
|
|
export async function getPendingCount() {
|
|
const queueCount = await db.syncQueue.where('status').equals('pending').count();
|
|
const filesCount = await db.pendingFiles.where('status').equals('pending').count();
|
|
return queueCount + filesCount;
|
|
}
|
|
|
|
/**
|
|
* Check if database has any cached workorders
|
|
*/
|
|
export async function hasOfflineData() {
|
|
const count = await db.workorders.count();
|
|
return count > 0;
|
|
}
|
|
|
|
/**
|
|
* Get storage usage estimate
|
|
*/
|
|
export async function getStorageEstimate() {
|
|
if (navigator.storage && navigator.storage.estimate) {
|
|
const estimate = await navigator.storage.estimate();
|
|
return {
|
|
usage: estimate.usage || 0,
|
|
quota: estimate.quota || 0,
|
|
usagePercent: estimate.quota ? Math.round((estimate.usage / estimate.quota) * 100) : 0
|
|
};
|
|
}
|
|
return { usage: 0, quota: 0, usagePercent: 0 };
|
|
}
|
|
|
|
/**
|
|
* Request persistent storage (important for iOS Safari)
|
|
*/
|
|
export async function requestPersistentStorage() {
|
|
if (navigator.storage && navigator.storage.persist) {
|
|
const isPersisted = await navigator.storage.persist();
|
|
console.log('[DB] Persistent storage:', isPersisted ? 'granted' : 'denied');
|
|
return isPersisted;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export default db;
|