@iankibetsh/shframework 5.8.3 → 5.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/dist/library.mjs.css +3 -0
- package/dist/library.js +183 -27
- package/dist/library.mjs +184 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ A robust table component that handles server-side pagination, searching, and cus
|
|
|
22
22
|
- **Auto-Label Generation**: Automatically generates human-readable labels from keys if not explicitly provided (e.g., `user.first_name` becomes "First Name").
|
|
23
23
|
- **Named Slots for Custom Formatting**: Use named slots for columns to provide custom formatting (e.g., `<template #age="{ row }">`).
|
|
24
24
|
- **Multi-Action Support**: Enable row selection and collective operations with a floating action bar.
|
|
25
|
+
- **Caching & Background Loading**: Uses IndexedDB to cache data. Shows cached data immediately while fetching fresh data in the background (enabled via `enableTableCache` config or `:cache="true"` prop).
|
|
26
|
+
- **Row Links**: Easily define clickable rows with dynamic placeholders (e.g., `:row-link="'/users/{id}'"`).
|
|
27
|
+
- **Search Optimization**: Automatically clears stale data and shows a spinner when searching to ensure fresh results.
|
|
25
28
|
- **Links & Actions**: Easily define column links and action buttons.
|
|
26
29
|
|
|
27
30
|
```html
|
package/dist/library.js
CHANGED
|
@@ -19,7 +19,7 @@ var Swal__default = /*#__PURE__*/_interopDefaultLegacy(Swal);
|
|
|
19
19
|
var NProgress__default = /*#__PURE__*/_interopDefaultLegacy(NProgress);
|
|
20
20
|
var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
|
|
21
21
|
|
|
22
|
-
function setItem (key, value) {
|
|
22
|
+
function setItem$1 (key, value) {
|
|
23
23
|
let toStore = value;
|
|
24
24
|
if (typeof value === 'object') {
|
|
25
25
|
toStore = JSON.stringify(value);
|
|
@@ -27,20 +27,20 @@ function setItem (key, value) {
|
|
|
27
27
|
return localStorage.setItem(key, toStore)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function getItem (key) {
|
|
30
|
+
function getItem$1 (key) {
|
|
31
31
|
try {
|
|
32
32
|
return JSON.parse(localStorage.getItem(key))
|
|
33
33
|
} catch (err) {
|
|
34
34
|
return localStorage.getItem(key)
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
function removeItem (key) {
|
|
37
|
+
function removeItem$1 (key) {
|
|
38
38
|
return localStorage.removeItem(key)
|
|
39
39
|
}
|
|
40
40
|
var shStorage = {
|
|
41
|
-
setItem,
|
|
42
|
-
getItem,
|
|
43
|
-
removeItem
|
|
41
|
+
setItem: setItem$1,
|
|
42
|
+
getItem: getItem$1,
|
|
43
|
+
removeItem: removeItem$1
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
function swalSuccess(message){
|
|
@@ -5464,6 +5464,112 @@ function render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
5464
5464
|
script$e.render = render$1;
|
|
5465
5465
|
script$e.__file = "src/lib/components/list_templates/Pagination.vue";
|
|
5466
5466
|
|
|
5467
|
+
const DB_NAME = 'ShTableCacheDB';
|
|
5468
|
+
const STORE_NAME = 'table_cache';
|
|
5469
|
+
const DB_VERSION = 1;
|
|
5470
|
+
|
|
5471
|
+
let dbPromise = null;
|
|
5472
|
+
|
|
5473
|
+
function getDB() {
|
|
5474
|
+
if (dbPromise) return dbPromise;
|
|
5475
|
+
|
|
5476
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
5477
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
5478
|
+
|
|
5479
|
+
request.onupgradeneeded = (event) => {
|
|
5480
|
+
const db = event.target.result;
|
|
5481
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
5482
|
+
db.createObjectStore(STORE_NAME);
|
|
5483
|
+
}
|
|
5484
|
+
};
|
|
5485
|
+
|
|
5486
|
+
request.onsuccess = (event) => {
|
|
5487
|
+
resolve(event.target.result);
|
|
5488
|
+
};
|
|
5489
|
+
|
|
5490
|
+
request.onerror = (event) => {
|
|
5491
|
+
console.error('IndexedDB error:', event.target.error);
|
|
5492
|
+
reject(event.target.error);
|
|
5493
|
+
};
|
|
5494
|
+
});
|
|
5495
|
+
|
|
5496
|
+
return dbPromise;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
async function setItem(key, value) {
|
|
5500
|
+
try {
|
|
5501
|
+
const db = await getDB();
|
|
5502
|
+
return new Promise((resolve, reject) => {
|
|
5503
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5504
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5505
|
+
const request = store.put(value, key);
|
|
5506
|
+
|
|
5507
|
+
request.onsuccess = () => resolve();
|
|
5508
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5509
|
+
});
|
|
5510
|
+
} catch (error) {
|
|
5511
|
+
console.error('ShIndexedDB setItem error:', error);
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
async function getItem(key, defaultValue = null) {
|
|
5516
|
+
try {
|
|
5517
|
+
const db = await getDB();
|
|
5518
|
+
return new Promise((resolve, reject) => {
|
|
5519
|
+
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
5520
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5521
|
+
const request = store.get(key);
|
|
5522
|
+
|
|
5523
|
+
request.onsuccess = (event) => {
|
|
5524
|
+
resolve(event.target.result !== undefined ? event.target.result : defaultValue);
|
|
5525
|
+
};
|
|
5526
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5527
|
+
});
|
|
5528
|
+
} catch (error) {
|
|
5529
|
+
console.error('ShIndexedDB getItem error:', error);
|
|
5530
|
+
return defaultValue;
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
|
|
5534
|
+
async function removeItem(key) {
|
|
5535
|
+
try {
|
|
5536
|
+
const db = await getDB();
|
|
5537
|
+
return new Promise((resolve, reject) => {
|
|
5538
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5539
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5540
|
+
const request = store.delete(key);
|
|
5541
|
+
|
|
5542
|
+
request.onsuccess = () => resolve();
|
|
5543
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5544
|
+
});
|
|
5545
|
+
} catch (error) {
|
|
5546
|
+
console.error('ShIndexedDB removeItem error:', error);
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
|
|
5550
|
+
async function clear() {
|
|
5551
|
+
try {
|
|
5552
|
+
const db = await getDB();
|
|
5553
|
+
return new Promise((resolve, reject) => {
|
|
5554
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5555
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5556
|
+
const request = store.clear();
|
|
5557
|
+
|
|
5558
|
+
request.onsuccess = () => resolve();
|
|
5559
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5560
|
+
});
|
|
5561
|
+
} catch (error) {
|
|
5562
|
+
console.error('ShIndexedDB clear error:', error);
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5566
|
+
var shIndexedDB = {
|
|
5567
|
+
setItem,
|
|
5568
|
+
getItem,
|
|
5569
|
+
removeItem,
|
|
5570
|
+
clear
|
|
5571
|
+
};
|
|
5572
|
+
|
|
5467
5573
|
const _hoisted_1$b = { class: "auto-table mt-2" };
|
|
5468
5574
|
const _hoisted_2$8 = {
|
|
5469
5575
|
key: 0,
|
|
@@ -5648,6 +5754,10 @@ var script$d = {
|
|
|
5648
5754
|
selectedRange: [Object, null],
|
|
5649
5755
|
noRecordsMessage: [String, null],
|
|
5650
5756
|
multiActions: { type: Array, default: () => [] },
|
|
5757
|
+
// Caching configuration: true to enable, false to disable. If null, respects global configure 'enableTableCache'
|
|
5758
|
+
cache: { type: Boolean, default: null },
|
|
5759
|
+
// Dynamic link for the entire row. Supports placeholders like '/user/{id}'
|
|
5760
|
+
rowLink: [String, null],
|
|
5651
5761
|
},
|
|
5652
5762
|
emits: ["rowSelected", "dataReloaded", "dataLoaded"],
|
|
5653
5763
|
setup(__props, { emit: __emit }) {
|
|
@@ -5706,7 +5816,7 @@ const hasRecordsSlot = vue.computed(() => !!slots.records);
|
|
|
5706
5816
|
const hasEmptySlot = vue.computed(() => !!slots.empty);
|
|
5707
5817
|
|
|
5708
5818
|
// --- Lifecycle
|
|
5709
|
-
vue.onMounted(() => {
|
|
5819
|
+
vue.onMounted(async () => {
|
|
5710
5820
|
if (props.headers) tableHeaders.value = props.headers;
|
|
5711
5821
|
|
|
5712
5822
|
if (props.actions?.actions) {
|
|
@@ -5715,7 +5825,7 @@ vue.onMounted(() => {
|
|
|
5715
5825
|
});
|
|
5716
5826
|
}
|
|
5717
5827
|
|
|
5718
|
-
if (
|
|
5828
|
+
if (shouldCache.value) await setCachedData();
|
|
5719
5829
|
|
|
5720
5830
|
reloadData();
|
|
5721
5831
|
|
|
@@ -5793,14 +5903,28 @@ const canvasClosed = () => {
|
|
|
5793
5903
|
selectedRecord.value = null;
|
|
5794
5904
|
};
|
|
5795
5905
|
|
|
5906
|
+
const router = vueRouter.useRouter();
|
|
5796
5907
|
const rowSelected = (row) => {
|
|
5797
5908
|
selectedRecord.value = null;
|
|
5798
5909
|
setTimeout(() => {
|
|
5799
5910
|
selectedRecord.value = row;
|
|
5800
5911
|
emit("rowSelected", row);
|
|
5912
|
+
if (props.rowLink) {
|
|
5913
|
+
router.push(replaceRowLink(props.rowLink, row));
|
|
5914
|
+
}
|
|
5801
5915
|
}, 100);
|
|
5802
5916
|
};
|
|
5803
5917
|
|
|
5918
|
+
const replaceRowLink = (p, obj) => {
|
|
5919
|
+
let path = p;
|
|
5920
|
+
const matches = path.match(/\{(.*?)\}/g);
|
|
5921
|
+
matches?.forEach((k) => {
|
|
5922
|
+
const key = k.replace("{", "").replace("}", "");
|
|
5923
|
+
path = path.replace(`{${key}}`, obj[key]);
|
|
5924
|
+
});
|
|
5925
|
+
return path;
|
|
5926
|
+
};
|
|
5927
|
+
|
|
5804
5928
|
const changeKey = (key, value) => {
|
|
5805
5929
|
if (key === "order_by") {
|
|
5806
5930
|
order_by.value = value;
|
|
@@ -5941,20 +6065,52 @@ const exportData = () => {
|
|
|
5941
6065
|
});
|
|
5942
6066
|
};
|
|
5943
6067
|
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
6068
|
+
// Attempts to load data from IndexedDB before API call
|
|
6069
|
+
const setCachedData = async () => {
|
|
6070
|
+
if (shouldCache.value) {
|
|
6071
|
+
const cached = await shIndexedDB.getItem(computedCacheKey.value, null);
|
|
6072
|
+
if (cached) {
|
|
6073
|
+
records.value = cached;
|
|
6074
|
+
// Set to 'done' immediately to show cached data without initial spinner
|
|
6075
|
+
loading.value = "done";
|
|
6076
|
+
}
|
|
5947
6077
|
}
|
|
5948
6078
|
};
|
|
5949
6079
|
|
|
6080
|
+
// Determines if caching should be active based on component props or global configuration
|
|
6081
|
+
const shouldCache = vue.computed(() => {
|
|
6082
|
+
if (props.cache !== null) return props.cache;
|
|
6083
|
+
return shRepo.getShConfig("enableTableCache", false);
|
|
6084
|
+
});
|
|
6085
|
+
|
|
6086
|
+
// Generates a unique, slug-safe key for IndexedDB storage
|
|
6087
|
+
const computedCacheKey = vue.computed(() => {
|
|
6088
|
+
if (props.cacheKey) return "sh_table_cache_" + props.cacheKey;
|
|
6089
|
+
let keyBase = props.endPoint || props.query || "default";
|
|
6090
|
+
|
|
6091
|
+
// Include date range in the key if active
|
|
6092
|
+
if (from.value || to.value || period.value) {
|
|
6093
|
+
keyBase += `_${from.value}_${to.value}_${period.value}`;
|
|
6094
|
+
}
|
|
6095
|
+
|
|
6096
|
+
const safeBase = keyBase.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
6097
|
+
return "sh_table_cache_" + safeBase;
|
|
6098
|
+
});
|
|
6099
|
+
|
|
5950
6100
|
// Main loader
|
|
6101
|
+
// Main data fetcher. Handles background updates when cache is present
|
|
5951
6102
|
const reloadData = (newPage, append) => {
|
|
5952
6103
|
if (typeof newPage !== "undefined") page.value = newPage;
|
|
5953
6104
|
|
|
5954
|
-
|
|
6105
|
+
// If we have cached data and not searching, we don't show the initial loading spinner
|
|
6106
|
+
if (shouldCache.value && records.value && records.value.length > 0 && !filter_value.value) {
|
|
5955
6107
|
loading.value = "done";
|
|
5956
6108
|
} else if (!append) {
|
|
5957
6109
|
loading.value = "loading";
|
|
6110
|
+
// Clear records when searching to ensure we show fresh results
|
|
6111
|
+
if (filter_value.value) {
|
|
6112
|
+
records.value = [];
|
|
6113
|
+
}
|
|
5958
6114
|
}
|
|
5959
6115
|
|
|
5960
6116
|
let data = {
|
|
@@ -5993,8 +6149,8 @@ const reloadData = (newPage, append) => {
|
|
|
5993
6149
|
const response = req.data.data;
|
|
5994
6150
|
emit("dataLoaded", response);
|
|
5995
6151
|
|
|
5996
|
-
if (page.value < 2 &&
|
|
5997
|
-
|
|
6152
|
+
if (page.value < 2 && shouldCache.value) {
|
|
6153
|
+
shIndexedDB.setItem(computedCacheKey.value, response.data);
|
|
5998
6154
|
}
|
|
5999
6155
|
|
|
6000
6156
|
pagination_data.value = {
|
|
@@ -6179,7 +6335,7 @@ return (_ctx, _cache) => {
|
|
|
6179
6335
|
: vue.createCommentVNode("v-if", true),
|
|
6180
6336
|
(hasDefaultSlot.value)
|
|
6181
6337
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 2 }, [
|
|
6182
|
-
(loading.value === 'loading')
|
|
6338
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6183
6339
|
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_9$2, [...(_cache[13] || (_cache[13] = [
|
|
6184
6340
|
vue.createElementVNode("div", {
|
|
6185
6341
|
class: "spinner-border",
|
|
@@ -6188,12 +6344,12 @@ return (_ctx, _cache) => {
|
|
|
6188
6344
|
vue.createElementVNode("span", { class: "visually-hidden" }, "Loading...")
|
|
6189
6345
|
], -1 /* CACHED */)
|
|
6190
6346
|
]))]))
|
|
6191
|
-
: (loading.value === 'error')
|
|
6347
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6192
6348
|
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_10$2, [
|
|
6193
6349
|
vue.createElementVNode("span", null, vue.toDisplayString(loading_error.value), 1 /* TEXT */)
|
|
6194
6350
|
]))
|
|
6195
6351
|
: vue.createCommentVNode("v-if", true),
|
|
6196
|
-
(loading.value === 'done')
|
|
6352
|
+
(loading.value === 'done' || records.value.length > 0)
|
|
6197
6353
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 2 }, [
|
|
6198
6354
|
(records.value.length === 0)
|
|
6199
6355
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 0 }, [
|
|
@@ -6217,7 +6373,7 @@ return (_ctx, _cache) => {
|
|
|
6217
6373
|
], 64 /* STABLE_FRAGMENT */))
|
|
6218
6374
|
: (hasRecordsSlot.value)
|
|
6219
6375
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 3 }, [
|
|
6220
|
-
(loading.value === 'loading' &&
|
|
6376
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6221
6377
|
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_12$1, [...(_cache[15] || (_cache[15] = [
|
|
6222
6378
|
vue.createElementVNode("div", {
|
|
6223
6379
|
class: "spinner-border",
|
|
@@ -6226,12 +6382,12 @@ return (_ctx, _cache) => {
|
|
|
6226
6382
|
vue.createElementVNode("span", { class: "visually-hidden" }, "Loading...")
|
|
6227
6383
|
], -1 /* CACHED */)
|
|
6228
6384
|
]))]))
|
|
6229
|
-
: (loading.value === 'error' &&
|
|
6385
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6230
6386
|
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_13$1, [
|
|
6231
6387
|
vue.createElementVNode("span", null, vue.toDisplayString(loading_error.value), 1 /* TEXT */)
|
|
6232
6388
|
]))
|
|
6233
6389
|
: vue.createCommentVNode("v-if", true),
|
|
6234
|
-
(loading.value === 'done' ||
|
|
6390
|
+
(loading.value === 'done' || records.value.length > 0)
|
|
6235
6391
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 2 }, [
|
|
6236
6392
|
(!records.value || records.value.length === 0)
|
|
6237
6393
|
? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 0 }, [
|
|
@@ -6308,7 +6464,7 @@ return (_ctx, _cache) => {
|
|
|
6308
6464
|
])
|
|
6309
6465
|
]),
|
|
6310
6466
|
vue.createElementVNode("tbody", _hoisted_22, [
|
|
6311
|
-
(loading.value === 'loading')
|
|
6467
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6312
6468
|
? (vue.openBlock(), vue.createElementBlock("tr", _hoisted_23, [
|
|
6313
6469
|
vue.createElementVNode("td", {
|
|
6314
6470
|
colspan:
|
|
@@ -6327,7 +6483,7 @@ return (_ctx, _cache) => {
|
|
|
6327
6483
|
], -1 /* CACHED */)
|
|
6328
6484
|
]))], 8 /* PROPS */, _hoisted_24)
|
|
6329
6485
|
]))
|
|
6330
|
-
: (loading.value === 'error')
|
|
6486
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6331
6487
|
? (vue.openBlock(), vue.createElementBlock("tr", _hoisted_25, [
|
|
6332
6488
|
vue.createElementVNode("td", {
|
|
6333
6489
|
colspan:
|
|
@@ -6358,7 +6514,7 @@ return (_ctx, _cache) => {
|
|
|
6358
6514
|
? (vue.openBlock(true), vue.createElementBlock(vue.Fragment, { key: 3 }, vue.renderList(records.value, (record, index) => {
|
|
6359
6515
|
return (vue.openBlock(), vue.createElementBlock("tr", {
|
|
6360
6516
|
key: record.id,
|
|
6361
|
-
class: vue.normalizeClass(record.class),
|
|
6517
|
+
class: vue.normalizeClass([record.class, props.rowLink ? 'cursor-pointer' : '']),
|
|
6362
6518
|
onClick: $event => (rowSelected(record))
|
|
6363
6519
|
}, [
|
|
6364
6520
|
(activeMultiActions.value.length > 0)
|
|
@@ -6455,7 +6611,7 @@ return (_ctx, _cache) => {
|
|
|
6455
6611
|
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(records.value, (record, index) => {
|
|
6456
6612
|
return (vue.openBlock(), vue.createElementBlock("div", {
|
|
6457
6613
|
key: record.id,
|
|
6458
|
-
class: "single-mobile-req bg-light p-3",
|
|
6614
|
+
class: vue.normalizeClass(["single-mobile-req bg-light p-3", props.rowLink ? 'cursor-pointer' : '']),
|
|
6459
6615
|
onClick: $event => (rowSelected(record))
|
|
6460
6616
|
}, [
|
|
6461
6617
|
(activeMultiActions.value.length > 0)
|
|
@@ -6560,7 +6716,7 @@ return (_ctx, _cache) => {
|
|
|
6560
6716
|
}, null, 8 /* PROPS */, ["actions", "record"])
|
|
6561
6717
|
]))
|
|
6562
6718
|
: vue.createCommentVNode("v-if", true)
|
|
6563
|
-
],
|
|
6719
|
+
], 10 /* CLASS, PROPS */, _hoisted_44))
|
|
6564
6720
|
}), 128 /* KEYED_FRAGMENT */))
|
|
6565
6721
|
]))
|
|
6566
6722
|
: (vue.openBlock(), vue.createElementBlock("div", _hoisted_62, [
|
|
@@ -8397,8 +8553,8 @@ const ShFrontend = {
|
|
|
8397
8553
|
}
|
|
8398
8554
|
//filter unwanted config items from options to be put in local storage
|
|
8399
8555
|
const removeKeys = ['formTextInput','router','shFormElementClasses'];
|
|
8400
|
-
const allowKeys = [];
|
|
8401
|
-
Object.keys(options).map(key=> ((!['string','integer','number'].includes(typeof options[key]) && !allowKeys.includes(key)) || removeKeys.includes(key)) && delete options[key]);
|
|
8556
|
+
const allowKeys = ['enableTableCache'];
|
|
8557
|
+
Object.keys(options).map(key=> ((!['string','integer','number','boolean'].includes(typeof options[key]) && !allowKeys.includes(key)) || removeKeys.includes(key)) && delete options[key]);
|
|
8402
8558
|
|
|
8403
8559
|
shStorage.setItem('ShConfig',options);
|
|
8404
8560
|
}
|
package/dist/library.mjs
CHANGED
|
@@ -5,10 +5,10 @@ import { Modal, Offcanvas } from 'bootstrap';
|
|
|
5
5
|
import NProgress from 'nprogress';
|
|
6
6
|
import { ref, computed, watch, onMounted, openBlock, createElementBlock, createElementVNode, createTextVNode, toDisplayString, createCommentVNode, withDirectives, Fragment, renderList, unref, vModelSelect, vModelText, normalizeClass, createBlock, resolveDynamicComponent, resolveComponent, inject, useTemplateRef, mergeProps, vShow, renderSlot, normalizeStyle, Teleport, createVNode, withCtx, useSlots, onBeforeUnmount, reactive, vModelCheckbox, withModifiers, resolveDirective, shallowRef, markRaw, isRef } from 'vue';
|
|
7
7
|
import _ from 'lodash';
|
|
8
|
-
import {
|
|
8
|
+
import { useRouter, useRoute } from 'vue-router';
|
|
9
9
|
import { defineStore, storeToRefs } from 'pinia';
|
|
10
10
|
|
|
11
|
-
function setItem (key, value) {
|
|
11
|
+
function setItem$1 (key, value) {
|
|
12
12
|
let toStore = value;
|
|
13
13
|
if (typeof value === 'object') {
|
|
14
14
|
toStore = JSON.stringify(value);
|
|
@@ -16,20 +16,20 @@ function setItem (key, value) {
|
|
|
16
16
|
return localStorage.setItem(key, toStore)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function getItem (key) {
|
|
19
|
+
function getItem$1 (key) {
|
|
20
20
|
try {
|
|
21
21
|
return JSON.parse(localStorage.getItem(key))
|
|
22
22
|
} catch (err) {
|
|
23
23
|
return localStorage.getItem(key)
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
function removeItem (key) {
|
|
26
|
+
function removeItem$1 (key) {
|
|
27
27
|
return localStorage.removeItem(key)
|
|
28
28
|
}
|
|
29
29
|
var shStorage = {
|
|
30
|
-
setItem,
|
|
31
|
-
getItem,
|
|
32
|
-
removeItem
|
|
30
|
+
setItem: setItem$1,
|
|
31
|
+
getItem: getItem$1,
|
|
32
|
+
removeItem: removeItem$1
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
function swalSuccess(message){
|
|
@@ -5453,6 +5453,112 @@ function render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
5453
5453
|
script$e.render = render$1;
|
|
5454
5454
|
script$e.__file = "src/lib/components/list_templates/Pagination.vue";
|
|
5455
5455
|
|
|
5456
|
+
const DB_NAME = 'ShTableCacheDB';
|
|
5457
|
+
const STORE_NAME = 'table_cache';
|
|
5458
|
+
const DB_VERSION = 1;
|
|
5459
|
+
|
|
5460
|
+
let dbPromise = null;
|
|
5461
|
+
|
|
5462
|
+
function getDB() {
|
|
5463
|
+
if (dbPromise) return dbPromise;
|
|
5464
|
+
|
|
5465
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
5466
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
5467
|
+
|
|
5468
|
+
request.onupgradeneeded = (event) => {
|
|
5469
|
+
const db = event.target.result;
|
|
5470
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
5471
|
+
db.createObjectStore(STORE_NAME);
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
|
|
5475
|
+
request.onsuccess = (event) => {
|
|
5476
|
+
resolve(event.target.result);
|
|
5477
|
+
};
|
|
5478
|
+
|
|
5479
|
+
request.onerror = (event) => {
|
|
5480
|
+
console.error('IndexedDB error:', event.target.error);
|
|
5481
|
+
reject(event.target.error);
|
|
5482
|
+
};
|
|
5483
|
+
});
|
|
5484
|
+
|
|
5485
|
+
return dbPromise;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5488
|
+
async function setItem(key, value) {
|
|
5489
|
+
try {
|
|
5490
|
+
const db = await getDB();
|
|
5491
|
+
return new Promise((resolve, reject) => {
|
|
5492
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5493
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5494
|
+
const request = store.put(value, key);
|
|
5495
|
+
|
|
5496
|
+
request.onsuccess = () => resolve();
|
|
5497
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5498
|
+
});
|
|
5499
|
+
} catch (error) {
|
|
5500
|
+
console.error('ShIndexedDB setItem error:', error);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
async function getItem(key, defaultValue = null) {
|
|
5505
|
+
try {
|
|
5506
|
+
const db = await getDB();
|
|
5507
|
+
return new Promise((resolve, reject) => {
|
|
5508
|
+
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
5509
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5510
|
+
const request = store.get(key);
|
|
5511
|
+
|
|
5512
|
+
request.onsuccess = (event) => {
|
|
5513
|
+
resolve(event.target.result !== undefined ? event.target.result : defaultValue);
|
|
5514
|
+
};
|
|
5515
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5516
|
+
});
|
|
5517
|
+
} catch (error) {
|
|
5518
|
+
console.error('ShIndexedDB getItem error:', error);
|
|
5519
|
+
return defaultValue;
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
|
|
5523
|
+
async function removeItem(key) {
|
|
5524
|
+
try {
|
|
5525
|
+
const db = await getDB();
|
|
5526
|
+
return new Promise((resolve, reject) => {
|
|
5527
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5528
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5529
|
+
const request = store.delete(key);
|
|
5530
|
+
|
|
5531
|
+
request.onsuccess = () => resolve();
|
|
5532
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5533
|
+
});
|
|
5534
|
+
} catch (error) {
|
|
5535
|
+
console.error('ShIndexedDB removeItem error:', error);
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
|
|
5539
|
+
async function clear() {
|
|
5540
|
+
try {
|
|
5541
|
+
const db = await getDB();
|
|
5542
|
+
return new Promise((resolve, reject) => {
|
|
5543
|
+
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
5544
|
+
const store = transaction.objectStore(STORE_NAME);
|
|
5545
|
+
const request = store.clear();
|
|
5546
|
+
|
|
5547
|
+
request.onsuccess = () => resolve();
|
|
5548
|
+
request.onerror = (event) => reject(event.target.error);
|
|
5549
|
+
});
|
|
5550
|
+
} catch (error) {
|
|
5551
|
+
console.error('ShIndexedDB clear error:', error);
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
|
|
5555
|
+
var shIndexedDB = {
|
|
5556
|
+
setItem,
|
|
5557
|
+
getItem,
|
|
5558
|
+
removeItem,
|
|
5559
|
+
clear
|
|
5560
|
+
};
|
|
5561
|
+
|
|
5456
5562
|
const _hoisted_1$b = { class: "auto-table mt-2" };
|
|
5457
5563
|
const _hoisted_2$8 = {
|
|
5458
5564
|
key: 0,
|
|
@@ -5637,6 +5743,10 @@ var script$d = {
|
|
|
5637
5743
|
selectedRange: [Object, null],
|
|
5638
5744
|
noRecordsMessage: [String, null],
|
|
5639
5745
|
multiActions: { type: Array, default: () => [] },
|
|
5746
|
+
// Caching configuration: true to enable, false to disable. If null, respects global configure 'enableTableCache'
|
|
5747
|
+
cache: { type: Boolean, default: null },
|
|
5748
|
+
// Dynamic link for the entire row. Supports placeholders like '/user/{id}'
|
|
5749
|
+
rowLink: [String, null],
|
|
5640
5750
|
},
|
|
5641
5751
|
emits: ["rowSelected", "dataReloaded", "dataLoaded"],
|
|
5642
5752
|
setup(__props, { emit: __emit }) {
|
|
@@ -5695,7 +5805,7 @@ const hasRecordsSlot = computed(() => !!slots.records);
|
|
|
5695
5805
|
const hasEmptySlot = computed(() => !!slots.empty);
|
|
5696
5806
|
|
|
5697
5807
|
// --- Lifecycle
|
|
5698
|
-
onMounted(() => {
|
|
5808
|
+
onMounted(async () => {
|
|
5699
5809
|
if (props.headers) tableHeaders.value = props.headers;
|
|
5700
5810
|
|
|
5701
5811
|
if (props.actions?.actions) {
|
|
@@ -5704,7 +5814,7 @@ onMounted(() => {
|
|
|
5704
5814
|
});
|
|
5705
5815
|
}
|
|
5706
5816
|
|
|
5707
|
-
if (
|
|
5817
|
+
if (shouldCache.value) await setCachedData();
|
|
5708
5818
|
|
|
5709
5819
|
reloadData();
|
|
5710
5820
|
|
|
@@ -5782,14 +5892,28 @@ const canvasClosed = () => {
|
|
|
5782
5892
|
selectedRecord.value = null;
|
|
5783
5893
|
};
|
|
5784
5894
|
|
|
5895
|
+
const router = useRouter();
|
|
5785
5896
|
const rowSelected = (row) => {
|
|
5786
5897
|
selectedRecord.value = null;
|
|
5787
5898
|
setTimeout(() => {
|
|
5788
5899
|
selectedRecord.value = row;
|
|
5789
5900
|
emit("rowSelected", row);
|
|
5901
|
+
if (props.rowLink) {
|
|
5902
|
+
router.push(replaceRowLink(props.rowLink, row));
|
|
5903
|
+
}
|
|
5790
5904
|
}, 100);
|
|
5791
5905
|
};
|
|
5792
5906
|
|
|
5907
|
+
const replaceRowLink = (p, obj) => {
|
|
5908
|
+
let path = p;
|
|
5909
|
+
const matches = path.match(/\{(.*?)\}/g);
|
|
5910
|
+
matches?.forEach((k) => {
|
|
5911
|
+
const key = k.replace("{", "").replace("}", "");
|
|
5912
|
+
path = path.replace(`{${key}}`, obj[key]);
|
|
5913
|
+
});
|
|
5914
|
+
return path;
|
|
5915
|
+
};
|
|
5916
|
+
|
|
5793
5917
|
const changeKey = (key, value) => {
|
|
5794
5918
|
if (key === "order_by") {
|
|
5795
5919
|
order_by.value = value;
|
|
@@ -5930,20 +6054,52 @@ const exportData = () => {
|
|
|
5930
6054
|
});
|
|
5931
6055
|
};
|
|
5932
6056
|
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
6057
|
+
// Attempts to load data from IndexedDB before API call
|
|
6058
|
+
const setCachedData = async () => {
|
|
6059
|
+
if (shouldCache.value) {
|
|
6060
|
+
const cached = await shIndexedDB.getItem(computedCacheKey.value, null);
|
|
6061
|
+
if (cached) {
|
|
6062
|
+
records.value = cached;
|
|
6063
|
+
// Set to 'done' immediately to show cached data without initial spinner
|
|
6064
|
+
loading.value = "done";
|
|
6065
|
+
}
|
|
5936
6066
|
}
|
|
5937
6067
|
};
|
|
5938
6068
|
|
|
6069
|
+
// Determines if caching should be active based on component props or global configuration
|
|
6070
|
+
const shouldCache = computed(() => {
|
|
6071
|
+
if (props.cache !== null) return props.cache;
|
|
6072
|
+
return shRepo.getShConfig("enableTableCache", false);
|
|
6073
|
+
});
|
|
6074
|
+
|
|
6075
|
+
// Generates a unique, slug-safe key for IndexedDB storage
|
|
6076
|
+
const computedCacheKey = computed(() => {
|
|
6077
|
+
if (props.cacheKey) return "sh_table_cache_" + props.cacheKey;
|
|
6078
|
+
let keyBase = props.endPoint || props.query || "default";
|
|
6079
|
+
|
|
6080
|
+
// Include date range in the key if active
|
|
6081
|
+
if (from.value || to.value || period.value) {
|
|
6082
|
+
keyBase += `_${from.value}_${to.value}_${period.value}`;
|
|
6083
|
+
}
|
|
6084
|
+
|
|
6085
|
+
const safeBase = keyBase.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
6086
|
+
return "sh_table_cache_" + safeBase;
|
|
6087
|
+
});
|
|
6088
|
+
|
|
5939
6089
|
// Main loader
|
|
6090
|
+
// Main data fetcher. Handles background updates when cache is present
|
|
5940
6091
|
const reloadData = (newPage, append) => {
|
|
5941
6092
|
if (typeof newPage !== "undefined") page.value = newPage;
|
|
5942
6093
|
|
|
5943
|
-
|
|
6094
|
+
// If we have cached data and not searching, we don't show the initial loading spinner
|
|
6095
|
+
if (shouldCache.value && records.value && records.value.length > 0 && !filter_value.value) {
|
|
5944
6096
|
loading.value = "done";
|
|
5945
6097
|
} else if (!append) {
|
|
5946
6098
|
loading.value = "loading";
|
|
6099
|
+
// Clear records when searching to ensure we show fresh results
|
|
6100
|
+
if (filter_value.value) {
|
|
6101
|
+
records.value = [];
|
|
6102
|
+
}
|
|
5947
6103
|
}
|
|
5948
6104
|
|
|
5949
6105
|
let data = {
|
|
@@ -5982,8 +6138,8 @@ const reloadData = (newPage, append) => {
|
|
|
5982
6138
|
const response = req.data.data;
|
|
5983
6139
|
emit("dataLoaded", response);
|
|
5984
6140
|
|
|
5985
|
-
if (page.value < 2 &&
|
|
5986
|
-
|
|
6141
|
+
if (page.value < 2 && shouldCache.value) {
|
|
6142
|
+
shIndexedDB.setItem(computedCacheKey.value, response.data);
|
|
5987
6143
|
}
|
|
5988
6144
|
|
|
5989
6145
|
pagination_data.value = {
|
|
@@ -6168,7 +6324,7 @@ return (_ctx, _cache) => {
|
|
|
6168
6324
|
: createCommentVNode("v-if", true),
|
|
6169
6325
|
(hasDefaultSlot.value)
|
|
6170
6326
|
? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
6171
|
-
(loading.value === 'loading')
|
|
6327
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6172
6328
|
? (openBlock(), createElementBlock("div", _hoisted_9$2, [...(_cache[13] || (_cache[13] = [
|
|
6173
6329
|
createElementVNode("div", {
|
|
6174
6330
|
class: "spinner-border",
|
|
@@ -6177,12 +6333,12 @@ return (_ctx, _cache) => {
|
|
|
6177
6333
|
createElementVNode("span", { class: "visually-hidden" }, "Loading...")
|
|
6178
6334
|
], -1 /* CACHED */)
|
|
6179
6335
|
]))]))
|
|
6180
|
-
: (loading.value === 'error')
|
|
6336
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6181
6337
|
? (openBlock(), createElementBlock("div", _hoisted_10$2, [
|
|
6182
6338
|
createElementVNode("span", null, toDisplayString(loading_error.value), 1 /* TEXT */)
|
|
6183
6339
|
]))
|
|
6184
6340
|
: createCommentVNode("v-if", true),
|
|
6185
|
-
(loading.value === 'done')
|
|
6341
|
+
(loading.value === 'done' || records.value.length > 0)
|
|
6186
6342
|
? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
6187
6343
|
(records.value.length === 0)
|
|
6188
6344
|
? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
@@ -6206,7 +6362,7 @@ return (_ctx, _cache) => {
|
|
|
6206
6362
|
], 64 /* STABLE_FRAGMENT */))
|
|
6207
6363
|
: (hasRecordsSlot.value)
|
|
6208
6364
|
? (openBlock(), createElementBlock(Fragment, { key: 3 }, [
|
|
6209
|
-
(loading.value === 'loading' &&
|
|
6365
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6210
6366
|
? (openBlock(), createElementBlock("div", _hoisted_12$1, [...(_cache[15] || (_cache[15] = [
|
|
6211
6367
|
createElementVNode("div", {
|
|
6212
6368
|
class: "spinner-border",
|
|
@@ -6215,12 +6371,12 @@ return (_ctx, _cache) => {
|
|
|
6215
6371
|
createElementVNode("span", { class: "visually-hidden" }, "Loading...")
|
|
6216
6372
|
], -1 /* CACHED */)
|
|
6217
6373
|
]))]))
|
|
6218
|
-
: (loading.value === 'error' &&
|
|
6374
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6219
6375
|
? (openBlock(), createElementBlock("div", _hoisted_13$1, [
|
|
6220
6376
|
createElementVNode("span", null, toDisplayString(loading_error.value), 1 /* TEXT */)
|
|
6221
6377
|
]))
|
|
6222
6378
|
: createCommentVNode("v-if", true),
|
|
6223
|
-
(loading.value === 'done' ||
|
|
6379
|
+
(loading.value === 'done' || records.value.length > 0)
|
|
6224
6380
|
? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
|
|
6225
6381
|
(!records.value || records.value.length === 0)
|
|
6226
6382
|
? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
@@ -6297,7 +6453,7 @@ return (_ctx, _cache) => {
|
|
|
6297
6453
|
])
|
|
6298
6454
|
]),
|
|
6299
6455
|
createElementVNode("tbody", _hoisted_22, [
|
|
6300
|
-
(loading.value === 'loading')
|
|
6456
|
+
(loading.value === 'loading' && records.value.length === 0)
|
|
6301
6457
|
? (openBlock(), createElementBlock("tr", _hoisted_23, [
|
|
6302
6458
|
createElementVNode("td", {
|
|
6303
6459
|
colspan:
|
|
@@ -6316,7 +6472,7 @@ return (_ctx, _cache) => {
|
|
|
6316
6472
|
], -1 /* CACHED */)
|
|
6317
6473
|
]))], 8 /* PROPS */, _hoisted_24)
|
|
6318
6474
|
]))
|
|
6319
|
-
: (loading.value === 'error')
|
|
6475
|
+
: (loading.value === 'error' && records.value.length === 0)
|
|
6320
6476
|
? (openBlock(), createElementBlock("tr", _hoisted_25, [
|
|
6321
6477
|
createElementVNode("td", {
|
|
6322
6478
|
colspan:
|
|
@@ -6347,7 +6503,7 @@ return (_ctx, _cache) => {
|
|
|
6347
6503
|
? (openBlock(true), createElementBlock(Fragment, { key: 3 }, renderList(records.value, (record, index) => {
|
|
6348
6504
|
return (openBlock(), createElementBlock("tr", {
|
|
6349
6505
|
key: record.id,
|
|
6350
|
-
class: normalizeClass(record.class),
|
|
6506
|
+
class: normalizeClass([record.class, props.rowLink ? 'cursor-pointer' : '']),
|
|
6351
6507
|
onClick: $event => (rowSelected(record))
|
|
6352
6508
|
}, [
|
|
6353
6509
|
(activeMultiActions.value.length > 0)
|
|
@@ -6444,7 +6600,7 @@ return (_ctx, _cache) => {
|
|
|
6444
6600
|
(openBlock(true), createElementBlock(Fragment, null, renderList(records.value, (record, index) => {
|
|
6445
6601
|
return (openBlock(), createElementBlock("div", {
|
|
6446
6602
|
key: record.id,
|
|
6447
|
-
class: "single-mobile-req bg-light p-3",
|
|
6603
|
+
class: normalizeClass(["single-mobile-req bg-light p-3", props.rowLink ? 'cursor-pointer' : '']),
|
|
6448
6604
|
onClick: $event => (rowSelected(record))
|
|
6449
6605
|
}, [
|
|
6450
6606
|
(activeMultiActions.value.length > 0)
|
|
@@ -6549,7 +6705,7 @@ return (_ctx, _cache) => {
|
|
|
6549
6705
|
}, null, 8 /* PROPS */, ["actions", "record"])
|
|
6550
6706
|
]))
|
|
6551
6707
|
: createCommentVNode("v-if", true)
|
|
6552
|
-
],
|
|
6708
|
+
], 10 /* CLASS, PROPS */, _hoisted_44))
|
|
6553
6709
|
}), 128 /* KEYED_FRAGMENT */))
|
|
6554
6710
|
]))
|
|
6555
6711
|
: (openBlock(), createElementBlock("div", _hoisted_62, [
|
|
@@ -8386,8 +8542,8 @@ const ShFrontend = {
|
|
|
8386
8542
|
}
|
|
8387
8543
|
//filter unwanted config items from options to be put in local storage
|
|
8388
8544
|
const removeKeys = ['formTextInput','router','shFormElementClasses'];
|
|
8389
|
-
const allowKeys = [];
|
|
8390
|
-
Object.keys(options).map(key=> ((!['string','integer','number'].includes(typeof options[key]) && !allowKeys.includes(key)) || removeKeys.includes(key)) && delete options[key]);
|
|
8545
|
+
const allowKeys = ['enableTableCache'];
|
|
8546
|
+
Object.keys(options).map(key=> ((!['string','integer','number','boolean'].includes(typeof options[key]) && !allowKeys.includes(key)) || removeKeys.includes(key)) && delete options[key]);
|
|
8391
8547
|
|
|
8392
8548
|
shStorage.setItem('ShConfig',options);
|
|
8393
8549
|
}
|