Working version before modification.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* user-db.js — IndexedDB storage for the user portal.
|
||||
*
|
||||
* Stores task data (including heavy base64 image dataUrls) in IndexedDB
|
||||
* instead of localStorage to avoid the ~5 MB browser quota.
|
||||
*
|
||||
* IndexedDB provides hundreds of MB of storage (browser-managed, quota-based)
|
||||
* which makes it suitable for image-heavy task data.
|
||||
*
|
||||
* Usage:
|
||||
* import { openUserDB, loadTaskData, saveTaskData, getStorageEstimate } from './user-db.js';
|
||||
*
|
||||
* await openUserDB(); // Call once on init
|
||||
* const data = await loadTaskData(); // Returns the full taskData object
|
||||
* await saveTaskData(data); // Persist updated taskData
|
||||
* const est = await getStorageEstimate(); // Get usage info
|
||||
*/
|
||||
|
||||
/* ── Constants ──────────────────────────────────────────────────────────── */
|
||||
|
||||
const DB_NAME = 'user-portal-db';
|
||||
const DB_VERSION = 1;
|
||||
const STORE_TASK_DATA = 'taskData';
|
||||
|
||||
/* ── Module-level DB reference ──────────────────────────────────────────── */
|
||||
|
||||
let db = null;
|
||||
|
||||
/* ── Open / create database ─────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Opens the IndexedDB database. Must be called once before any read/write.
|
||||
* Creates the object store on first run or version upgrade.
|
||||
*/
|
||||
export function openUserDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
|
||||
request.onsuccess = () => {
|
||||
db = request.result;
|
||||
resolve(db);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const database = request.result;
|
||||
/* Single store keyed by taskId — each entry holds one task's data */
|
||||
if (!database.objectStoreNames.contains(STORE_TASK_DATA)) {
|
||||
database.createObjectStore(STORE_TASK_DATA, { keyPath: 'taskId' });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Read all task data ─────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Loads all task data from IndexedDB and returns it as a plain object
|
||||
* keyed by taskId (same shape as the old localStorage structure).
|
||||
*
|
||||
* @returns {Promise<Object>} e.g. { "task-123": { visitDate: "", records: {...} }, ... }
|
||||
*/
|
||||
export function loadTaskData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) { resolve({}); return; }
|
||||
const tx = db.transaction(STORE_TASK_DATA, 'readonly');
|
||||
const store = tx.objectStore(STORE_TASK_DATA);
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => {
|
||||
const result = {};
|
||||
for (const entry of request.result) {
|
||||
const { taskId, ...data } = entry;
|
||||
result[taskId] = data;
|
||||
}
|
||||
resolve(result);
|
||||
};
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Save all task data ─────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Persists the full taskData object into IndexedDB.
|
||||
* Each taskId becomes a separate record in the store for efficient access.
|
||||
*
|
||||
* @param {Object} taskData - Object keyed by taskId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function saveTaskData(taskData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) { reject(new Error('Database not open')); return; }
|
||||
const tx = db.transaction(STORE_TASK_DATA, 'readwrite');
|
||||
const store = tx.objectStore(STORE_TASK_DATA);
|
||||
|
||||
for (const [taskId, data] of Object.entries(taskData)) {
|
||||
store.put({ taskId, ...data });
|
||||
}
|
||||
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Save single task entry ─────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Saves or updates a single task's data. More efficient than saving everything
|
||||
* when only one task changed.
|
||||
*
|
||||
* @param {string} taskId
|
||||
* @param {Object} data - The task data (visitDate, records, etc.)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function saveOneTaskData(taskId, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) { reject(new Error('Database not open')); return; }
|
||||
const tx = db.transaction(STORE_TASK_DATA, 'readwrite');
|
||||
const store = tx.objectStore(STORE_TASK_DATA);
|
||||
store.put({ taskId, ...data });
|
||||
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Delete a task entry ────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Removes a single task's data from IndexedDB.
|
||||
*
|
||||
* @param {string} taskId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function deleteTaskData(taskId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!db) { reject(new Error('Database not open')); return; }
|
||||
const tx = db.transaction(STORE_TASK_DATA, 'readwrite');
|
||||
const store = tx.objectStore(STORE_TASK_DATA);
|
||||
store.delete(taskId);
|
||||
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Storage estimate ───────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Returns an estimate of IndexedDB usage (if the StorageManager API is available).
|
||||
* Falls back to counting serialized task data size.
|
||||
*
|
||||
* @param {Object} taskData - Current in-memory taskData for fallback sizing
|
||||
* @returns {Promise<{usedMB: string, quotaMB: string, pct: number}>}
|
||||
*/
|
||||
export async function getStorageEstimate(taskData) {
|
||||
/* Try the modern Storage API (available in secure contexts) */
|
||||
if (navigator.storage && navigator.storage.estimate) {
|
||||
const est = await navigator.storage.estimate();
|
||||
const usedMB = ((est.usage || 0) / (1024 * 1024)).toFixed(2);
|
||||
const quotaMB = ((est.quota || 0) / (1024 * 1024)).toFixed(0);
|
||||
const pct = est.quota ? Math.min(100, ((est.usage / est.quota) * 100)) : 0;
|
||||
return { usedMB, quotaMB, pct: Math.round(pct) };
|
||||
}
|
||||
|
||||
/* Fallback: estimate from serialized data */
|
||||
const json = JSON.stringify(taskData || {});
|
||||
const bytes = json.length * 2; /* UTF-16 */
|
||||
const usedMB = (bytes / (1024 * 1024)).toFixed(2);
|
||||
return { usedMB, quotaMB: '∞', pct: 0 };
|
||||
}
|
||||
Reference in New Issue
Block a user