Files
thetool/public/mobile/shared/db.js
2026-01-27 10:35:15 +01:00

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;