feat(pos): add 5 quick improvements — dark mode, email quotes, barcode scan, returns, offline catalog
1. Auto dark mode: detect system prefers-color-scheme, auto-switch industrial/modern theme 2. Email quotation endpoint: POST /quotations/:id/email sends HTML email via SMTP 3. Camera barcode scanner: BarcodeDetector API with getUserMedia overlay in catalog 4. Returns with warranty: POST /returns endpoint with stock restoration and sale status tracking 5. Partial offline catalog: cache top 500 parts in IndexedDB, search when offline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,10 @@
|
||||
'use strict';
|
||||
|
||||
var DB_NAME = 'nexus_pos_offline';
|
||||
var DB_VERSION = 1;
|
||||
var QUEUE_STORE = 'sync_queue';
|
||||
var INVENTORY_STORE = 'inventory_cache';
|
||||
var DB_VERSION = 2;
|
||||
var QUEUE_STORE = 'sync_queue';
|
||||
var INVENTORY_STORE = 'inventory_cache';
|
||||
var TOP_PARTS_STORE = 'cached_parts';
|
||||
|
||||
var db = null;
|
||||
|
||||
@@ -26,6 +27,11 @@
|
||||
inv.createIndex('sku', 'sku', { unique: false });
|
||||
inv.createIndex('name', 'name', { unique: false });
|
||||
}
|
||||
if (!d.objectStoreNames.contains(TOP_PARTS_STORE)) {
|
||||
var tp = d.createObjectStore(TOP_PARTS_STORE, { keyPath: 'part_number' });
|
||||
tp.createIndex('name', 'name', { unique: false });
|
||||
tp.createIndex('category', 'category', { unique: false });
|
||||
}
|
||||
};
|
||||
req.onsuccess = function (e) {
|
||||
db = e.target.result;
|
||||
@@ -156,6 +162,52 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Top parts cache (offline catalog) ────────────────────────
|
||||
function cacheTopParts() {
|
||||
return fetch('/pos/api/sync/top-parts').then(function (resp) {
|
||||
if (!resp.ok) throw new Error('Sync top-parts failed: ' + resp.status);
|
||||
return resp.json();
|
||||
}).then(function (data) {
|
||||
var parts = data.parts || [];
|
||||
return openDB().then(function (d) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var tx = d.transaction(TOP_PARTS_STORE, 'readwrite');
|
||||
var store = tx.objectStore(TOP_PARTS_STORE);
|
||||
store.clear();
|
||||
parts.forEach(function (p) { store.put(p); });
|
||||
tx.oncomplete = function () {
|
||||
console.log('[SyncEngine] Cached ' + parts.length + ' top parts');
|
||||
resolve(parts.length);
|
||||
};
|
||||
tx.onerror = function () { reject(tx.error); };
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function searchCachedParts(query) {
|
||||
return openDB().then(function (d) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var tx = d.transaction(TOP_PARTS_STORE, 'readonly');
|
||||
var req = tx.objectStore(TOP_PARTS_STORE).getAll();
|
||||
req.onsuccess = function () {
|
||||
var all = req.result;
|
||||
if (!query) { resolve(all); return; }
|
||||
|
||||
var q = query.toLowerCase();
|
||||
var filtered = all.filter(function (p) {
|
||||
return (p.part_number && p.part_number.toLowerCase().indexOf(q) !== -1) ||
|
||||
(p.name && p.name.toLowerCase().indexOf(q) !== -1) ||
|
||||
(p.category && p.category.toLowerCase().indexOf(q) !== -1) ||
|
||||
(p.brand && p.brand.toLowerCase().indexOf(q) !== -1);
|
||||
});
|
||||
resolve(filtered);
|
||||
};
|
||||
req.onerror = function () { reject(req.error); };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Connectivity helpers ─────────────────────────────────────
|
||||
function isOnline() {
|
||||
return navigator.onLine;
|
||||
@@ -193,12 +245,14 @@
|
||||
|
||||
// ─── Public API ───────────────────────────────────────────────
|
||||
window.SyncEngine = {
|
||||
queueOperation: queueOperation,
|
||||
processQueue: processQueue,
|
||||
getQueueCount: getQueueCount,
|
||||
isOnline: isOnline,
|
||||
cacheInventory: cacheInventory,
|
||||
getCachedInventory: getCachedInventory
|
||||
queueOperation: queueOperation,
|
||||
processQueue: processQueue,
|
||||
getQueueCount: getQueueCount,
|
||||
isOnline: isOnline,
|
||||
cacheInventory: cacheInventory,
|
||||
getCachedInventory: getCachedInventory,
|
||||
cacheTopParts: cacheTopParts,
|
||||
searchCachedParts: searchCachedParts
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user