initial: bootstrap from BukidBountyApp base
This commit is contained in:
1723
resources/js/utils/Legacy/UIALT.js
Normal file
1723
resources/js/utils/Legacy/UIALT.js
Normal file
File diff suppressed because it is too large
Load Diff
41
resources/js/utils/Legacy/domHelpers.js
Normal file
41
resources/js/utils/Legacy/domHelpers.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @deprecated Use Vue refs or reactive state instead
|
||||
*/
|
||||
export function getElementHtml(id) {
|
||||
return document.getElementById(id)?.innerHTML ?? ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use Vue refs or reactive state instead
|
||||
*/
|
||||
export function setElementHtml(id, html) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) el.innerHTML = html
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use v-model instead
|
||||
*/
|
||||
export function getElementValue(id) {
|
||||
return document.getElementById(id)?.value ?? ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use v-model instead
|
||||
*/
|
||||
export function setElementValue(id, value) {
|
||||
const el = document.getElementById(id)
|
||||
if (el) el.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use reactive state instead of DOM removal
|
||||
*/
|
||||
export function removeElementById(id) {
|
||||
const el = document.getElementById(id)
|
||||
if (el?.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
133
resources/js/utils/RequestData.js
Normal file
133
resources/js/utils/RequestData.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// resources/js/utils/RequestData.js
|
||||
import { useHashKeyCache } from '../composables/useHashKeyCache.js';
|
||||
|
||||
/**
|
||||
* Modern implementation of the RequestData pattern.
|
||||
* Supports method chaining and integration with HashKeyCache.
|
||||
*/
|
||||
export class RequestData {
|
||||
constructor(withHashCheck = false) {
|
||||
this._url = '';
|
||||
this._type = 'GET';
|
||||
this._data = null;
|
||||
this._success = null;
|
||||
this._error = null;
|
||||
this._fromVarCache = false;
|
||||
this._withHashCheck = withHashCheck;
|
||||
this._hashCache = useHashKeyCache();
|
||||
}
|
||||
|
||||
/** Set the request URL */
|
||||
url(u) {
|
||||
this._url = u;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the HTTP method (GET, POST, etc.) */
|
||||
type(t) {
|
||||
this._type = t.toUpperCase();
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the request payload */
|
||||
data(d) {
|
||||
this._data = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the success callback */
|
||||
success(callback) {
|
||||
this._success = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the error callback */
|
||||
error(callback) {
|
||||
this._error = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Enable or disable cache-first behavior */
|
||||
fromVarCache(bool) {
|
||||
this._fromVarCache = bool;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Execute the request */
|
||||
async go() {
|
||||
if (!this._url) {
|
||||
console.error('[RequestData] URL is missing');
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Check HashKey/Payload in URL first if applicable
|
||||
const urlMatch = this._hashCache.parseHashUrl(this._url);
|
||||
if (urlMatch.type !== 'none' && urlMatch.data) {
|
||||
if (this._success) this._success(urlMatch.data, urlMatch.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Check Var Cache (in-memory) if requested
|
||||
if (this._fromVarCache) {
|
||||
const cachedData = this._hashCache.getHashData(this._url);
|
||||
if (cachedData) {
|
||||
if (this._success) this._success(cachedData);
|
||||
// Revalidate in background (Stale-While-Revalidate pattern)
|
||||
this._fetchFromServer(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Perform network request
|
||||
return this._fetchFromServer();
|
||||
}
|
||||
|
||||
/** Internal fetch implementation */
|
||||
async _fetchFromServer(background = false) {
|
||||
try {
|
||||
const options = {
|
||||
method: this._type,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
};
|
||||
|
||||
if (this._data && (this._type === 'POST' || this._type === 'PUT')) {
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
options.body = JSON.stringify(this._data);
|
||||
}
|
||||
|
||||
const response = await fetch(this._url, options);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Update cache
|
||||
if (this._fromVarCache) {
|
||||
this._hashCache.setHashData(this._url, result);
|
||||
}
|
||||
|
||||
// If background revalidation found non-identical data, you might want to call success again
|
||||
// or use Pinia to update the UI reactively.
|
||||
if (this._success) {
|
||||
// For simplicity, we call success again if it's a background fetch
|
||||
// but usually you want a way to avoid jitter if data is the same.
|
||||
this._success(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (!background && this._error) {
|
||||
this._error(err);
|
||||
}
|
||||
if (background) {
|
||||
console.warn('[RequestData] Background revalidation failed:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrapper function for easier instantiation */
|
||||
export function request(withHashCheck = false) {
|
||||
return new RequestData(withHashCheck);
|
||||
}
|
||||
19
resources/js/utils/UserTypes.js
Normal file
19
resources/js/utils/UserTypes.js
Normal file
@@ -0,0 +1,19 @@
|
||||
export const UserTypes = {
|
||||
ULTIMATE: 'ult',
|
||||
SUPER_OPERATOR: 'super operator',
|
||||
OPERATOR: 'operator',
|
||||
COORDINATOR: 'coordinator',
|
||||
COOP_OFFICER: 'coop officer',
|
||||
COOP_MEMBER: 'coop member',
|
||||
SUPPLIER_OVERSEER: 'supplier overseer',
|
||||
WHOLESALE_BUYER: 'wholesale buyer',
|
||||
SUPPLIER: 'supplier',
|
||||
STORE_OWNER: 'store owner',
|
||||
STORE_MANAGER: 'store manager',
|
||||
USER: 'user',
|
||||
RIDER: 'rider',
|
||||
AUDIT: 'audit',
|
||||
POS_TERMINAL: 'pos terminal',
|
||||
ANY_USER: 'default',
|
||||
PUBLIC: 'public'
|
||||
};
|
||||
66
resources/js/utils/array.js
Normal file
66
resources/js/utils/array.js
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Finds duplicate sub-arrays in a multidimensional array.
|
||||
*
|
||||
* @param {Array<Array>} arr - The multidimensional array to check
|
||||
* @returns {Array<Array>|false} - Array of duplicates or false if none
|
||||
*
|
||||
* @example
|
||||
* findDuplicatesInMultidimensionalArray([[1,2],[3,4],[1,2]])
|
||||
* // returns [[1,2]]
|
||||
*/
|
||||
export function findDuplicatesInMultidimensionalArray(arr) {
|
||||
const seen = new Set()
|
||||
const duplicates = []
|
||||
|
||||
for (const subArr of arr) {
|
||||
const key = JSON.stringify(subArr)
|
||||
if (seen.has(key)) {
|
||||
duplicates.push(subArr)
|
||||
} else {
|
||||
seen.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
return duplicates.length ? duplicates : false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Joins array elements with a hyphen.
|
||||
*
|
||||
* @param {Array<string|number>} arr
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* implodeArrayWithHyphen([1,2,3]) // "1-2-3"
|
||||
*/
|
||||
export function implodeArrayWithHyphen(arr) {
|
||||
if (!Array.isArray(arr)) return ''
|
||||
return arr.join('-')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shuffles an array in-place using the Fisher–Yates algorithm.
|
||||
*
|
||||
* @param {Array} array - The array to shuffle
|
||||
* @returns {Array} The shuffled array
|
||||
*
|
||||
* @example
|
||||
* shuffleArray([1,2,3,4])
|
||||
* // might return [3,1,4,2]
|
||||
*/
|
||||
export function shuffleArray(array) {
|
||||
if (!Array.isArray(array)) return []
|
||||
|
||||
let currentIndex = array.length
|
||||
let randomIndex
|
||||
|
||||
while (currentIndex !== 0) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex)
|
||||
currentIndex--
|
||||
;[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
26
resources/js/utils/cdnAsset.js
Normal file
26
resources/js/utils/cdnAsset.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import manifest from '../../cdn-manifest.json';
|
||||
|
||||
const ASSETS = manifest.assets || {};
|
||||
|
||||
function resolveBase() {
|
||||
if (typeof window !== 'undefined' && window.__CDN_BASE__) {
|
||||
return window.__CDN_BASE__;
|
||||
}
|
||||
// Fallback for SSR or pre-mount calls; the SHA matches resources/cdn-manifest.json
|
||||
return `https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@${manifest.version}`;
|
||||
}
|
||||
|
||||
export function cdnAsset(logicalName) {
|
||||
const path = ASSETS[logicalName];
|
||||
const base = resolveBase();
|
||||
if (!path) {
|
||||
return `${base}/missing/${logicalName}`;
|
||||
}
|
||||
return `${base}/${path}`;
|
||||
}
|
||||
|
||||
export function cdnBase() {
|
||||
return resolveBase();
|
||||
}
|
||||
|
||||
export default cdnAsset;
|
||||
25
resources/js/utils/currency.js
Normal file
25
resources/js/utils/currency.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Formats a number as Philippine Peso currency (PHP) with commas and optional decimals.
|
||||
*
|
||||
* @param {number} number - The numeric value to format
|
||||
* @param {boolean} [withDecimal=true] - Whether to include decimal places
|
||||
* @returns {string} - Formatted currency string (e.g., "P1,234.56")
|
||||
*
|
||||
* @example
|
||||
* formatCurrency(1234.56) // "P1,234.56"
|
||||
* formatCurrency(1234.56, false) // "P1,234"
|
||||
*/
|
||||
export function formatCurrency(number, withDecimal = true) {
|
||||
if (isNaN(number)) return 'P0'
|
||||
|
||||
let [integerPart, decimalPart] = Number(number)
|
||||
.toFixed(2)
|
||||
.toString()
|
||||
.split('.')
|
||||
|
||||
if (!withDecimal) decimalPart = ''
|
||||
|
||||
integerPart = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
|
||||
return `P${integerPart}${decimalPart ? '.' + decimalPart : ''}`
|
||||
}
|
||||
105
resources/js/utils/date.js
Normal file
105
resources/js/utils/date.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Converts a 24-hour time string to 12-hour format.
|
||||
*
|
||||
* @param {string} time24 - Time in HH:mm or HH:mm:ss format
|
||||
* @returns {string} Time in 12-hour format (e.g. "3:45 PM")
|
||||
*
|
||||
* @example
|
||||
* convertTo12HRTime('14:30') // "2:30 PM"
|
||||
*/
|
||||
export function convertTo12HRTime(time24) {
|
||||
if (!time24) return ''
|
||||
|
||||
return new Date(`1970-01-01T${time24}Z`).toLocaleTimeString('en-US', {
|
||||
timeZone: 'UTC',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a Date object into "Mon DD, YYYY".
|
||||
*
|
||||
* @param {Date} date
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* formatDate(new Date()) // "Jan 31, 2026"
|
||||
*/
|
||||
export function formatDate(date) {
|
||||
if (!(date instanceof Date)) return ''
|
||||
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns today's date in GMT+8 timezone in "YYYY-MM-DD" format.
|
||||
*
|
||||
* @returns {string} - Date string in GMT+8
|
||||
*
|
||||
* @example
|
||||
* getDateInGMT8() // "2026-01-31"
|
||||
*/
|
||||
export function getDateInGMT8() {
|
||||
const now = new Date()
|
||||
const utcTime = now.getTime()
|
||||
const gmt8Offset = 8 * 60 * 60 * 1000
|
||||
const gmt8Time = new Date(utcTime + gmt8Offset)
|
||||
|
||||
return gmt8Time.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts a date-time string into a readable format.
|
||||
*
|
||||
* Supports:
|
||||
* - "YYYY-MM-DD"
|
||||
* - "YYYY-MM-DD HH:mm:ss"
|
||||
* - ISO strings
|
||||
*
|
||||
* @param {string} dateTimeString
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* formatDateTimetoReadable('2026-01-31 14:30')
|
||||
* // "January 31, 2026 2:30PM"
|
||||
*/
|
||||
export function formatDateTimetoReadable(dateTimeString) {
|
||||
if (!dateTimeString) return ''
|
||||
|
||||
const normalized = dateTimeString.includes(' ')
|
||||
? dateTimeString.replace(' ', 'T')
|
||||
: dateTimeString
|
||||
|
||||
const date = new Date(normalized)
|
||||
if (isNaN(date.getTime())) return ''
|
||||
|
||||
const datePart = date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
|
||||
// Only show time if original string had time
|
||||
if (!dateTimeString.includes(' ')) {
|
||||
return datePart
|
||||
}
|
||||
|
||||
let hours = date.getHours()
|
||||
const minutes = date.getMinutes()
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM'
|
||||
|
||||
hours = hours % 12 || 12
|
||||
const paddedMinutes = minutes.toString().padStart(2, '0')
|
||||
|
||||
return `${datePart} ${hours}:${paddedMinutes}${ampm}`
|
||||
}
|
||||
25
resources/js/utils/deepEqual.js
Normal file
25
resources/js/utils/deepEqual.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export function deepEqual(a, b) {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!deepEqual(a[i], b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof a === 'object' && typeof b === 'object') {
|
||||
const keysA = Object.keys(a);
|
||||
const keysB = Object.keys(b);
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
|
||||
for (let key of keysA) {
|
||||
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return a === b;
|
||||
}
|
||||
31
resources/js/utils/executeRequest.js
Normal file
31
resources/js/utils/executeRequest.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// utils/executeRequest.js
|
||||
export async function executeRequest(request) {
|
||||
// URL string → GET
|
||||
if (typeof request === 'string') {
|
||||
const res = await fetch(request)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
// Request object
|
||||
const {
|
||||
url,
|
||||
method = 'GET',
|
||||
data = null,
|
||||
headers = {}
|
||||
} = request
|
||||
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
}
|
||||
}
|
||||
|
||||
if (method !== 'GET' && data !== null) {
|
||||
options.body = JSON.stringify(data)
|
||||
}
|
||||
|
||||
const res = await fetch(url, options)
|
||||
return res.json()
|
||||
}
|
||||
67
resources/js/utils/fetchWithCache.js
Normal file
67
resources/js/utils/fetchWithCache.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { deepEqual } from '@/utils/deepEqual'
|
||||
import { executeRequest } from './executeRequest'
|
||||
|
||||
const cacheStore = new Map()
|
||||
|
||||
/**
|
||||
* Cache-first, stale-while-revalidate data fetcher.
|
||||
*
|
||||
* Supports:
|
||||
* - URL string (GET)
|
||||
* - Request object (GET / POST)
|
||||
* - Custom async fetcher function
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.key - Unique cache key
|
||||
* @param {string|Object} [options.request] - URL or request config
|
||||
* @param {Function} [options.fetcher] - Custom async function
|
||||
* @param {Function} options.onUpdate - Callback with data
|
||||
*
|
||||
* @example
|
||||
* fetchWithCache({
|
||||
* key: 'users.list',
|
||||
* request: '/api/users',
|
||||
* onUpdate: (data, source) => {
|
||||
* users.value = data
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
export async function fetchWithCache({
|
||||
key,
|
||||
request = null,
|
||||
fetcher = null,
|
||||
onUpdate = null
|
||||
}) {
|
||||
if (!key) {
|
||||
throw new Error('fetchWithCache requires a cache key')
|
||||
}
|
||||
|
||||
// 1️⃣ Emit cached data immediately
|
||||
if (cacheStore.has(key)) {
|
||||
onUpdate?.(cacheStore.get(key), 'cache')
|
||||
}
|
||||
|
||||
// 2️⃣ Build fetcher if not provided
|
||||
let resolvedFetcher = fetcher
|
||||
|
||||
if (!resolvedFetcher && request) {
|
||||
resolvedFetcher = () => executeRequest(request)
|
||||
}
|
||||
|
||||
if (typeof resolvedFetcher !== 'function') {
|
||||
throw new Error('fetchWithCache requires either fetcher or request')
|
||||
}
|
||||
|
||||
// 3️⃣ Fetch fresh data
|
||||
try {
|
||||
const fresh = await resolvedFetcher()
|
||||
const cached = cacheStore.get(key)
|
||||
|
||||
if (!cached || !deepEqual(cached, fresh)) {
|
||||
cacheStore.set(key, fresh)
|
||||
onUpdate?.(fresh, 'fresh')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[fetchWithCache] fetch failed:', err)
|
||||
}
|
||||
}
|
||||
62
resources/js/utils/image.js
Normal file
62
resources/js/utils/image.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Resize an image file while preserving aspect ratio.
|
||||
*
|
||||
* @param {File|Blob} file
|
||||
* @param {number} maxWidth
|
||||
* @param {number} maxHeight
|
||||
* @param {number} [quality=0.7] - JPEG/WebP quality (0–1)
|
||||
* @returns {Promise<Blob>}
|
||||
*
|
||||
* @example
|
||||
* const resized = await resizeImage(file, 800, 800)
|
||||
*/
|
||||
export function resizeImage(file, maxWidth, maxHeight, quality = 0.7) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(file instanceof Blob)) {
|
||||
reject(new Error('Invalid file'))
|
||||
return
|
||||
}
|
||||
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = e => {
|
||||
const img = new Image()
|
||||
|
||||
img.onload = () => {
|
||||
let { width, height } = img
|
||||
|
||||
const widthRatio = maxWidth / width
|
||||
const heightRatio = maxHeight / height
|
||||
const ratio = Math.min(widthRatio, heightRatio, 1)
|
||||
|
||||
width = Math.round(width * ratio)
|
||||
height = Math.round(height * ratio)
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(img, 0, 0, width, height)
|
||||
|
||||
canvas.toBlob(
|
||||
blob => {
|
||||
if (!blob) {
|
||||
reject(new Error('Image resize failed'))
|
||||
return
|
||||
}
|
||||
resolve(blob)
|
||||
},
|
||||
file.type || 'image/jpeg',
|
||||
quality
|
||||
)
|
||||
}
|
||||
|
||||
img.onerror = () => reject(new Error('Invalid image'))
|
||||
img.src = e.target.result
|
||||
}
|
||||
|
||||
reader.onerror = () => reject(new Error('File read error'))
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
822
resources/js/utils/libFunctions.js
Normal file
822
resources/js/utils/libFunctions.js
Normal file
@@ -0,0 +1,822 @@
|
||||
var content_div_id = "content_wrapper";
|
||||
var historylist = [];
|
||||
var targetlist = [];
|
||||
var fileBlobURLList = {};
|
||||
|
||||
|
||||
if (typeof currentPage === 'undefined') {
|
||||
var currentPage = '';
|
||||
}
|
||||
|
||||
|
||||
function InitDataPageFuncOBJ() {
|
||||
if (typeof LoadDataPageFunc === 'undefined') {
|
||||
LoadDataPageFunc = {};
|
||||
logDev('Initialize LoadDataPageFunc');
|
||||
}
|
||||
}
|
||||
|
||||
function newPageLoadDataPageFuncOBJ() {
|
||||
LoadDataPageFunc = {};
|
||||
LoadDataPageFunc.PageTitle = '';
|
||||
LoadDataPageFunc.MainDetailsURL = '';
|
||||
LoadDataPageFunc.Details = {};
|
||||
LoadDataPageFunc.Settings = {};
|
||||
LoadDataPageFunc.currentPage = '';
|
||||
LoadDataPageFunc.Disabled = null;
|
||||
defaultBackOnclick = null;
|
||||
LoadDataPageFunc.URL = {};
|
||||
LoadDataPageFunc.URL.Details = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
window.addEventListener('load', function() {
|
||||
window.history.pushState({}, ''); window.history.pushState({}, ''); window.history.pushState({}, ''); window.history.pushState({}, '');
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function() {
|
||||
window.history.pushState({}, ''); window.history.pushState({}, ''); window.history.pushState({}, ''); window.history.pushState({}, ''); window.history.pushState({}, '');
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
function loadandexecute(targeturl, targetid = "main-body", targethtml = '') {
|
||||
CurrentPageisLoading = true;
|
||||
// console.log('loadandexecute started');
|
||||
|
||||
if (!targetid) { targetid = 'main-body'; }
|
||||
|
||||
if (targethtml) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
targethtml = base64Decode(targethtml);
|
||||
$("#" + targetid).html(targethtml);
|
||||
CurrentPageisLoading = false;
|
||||
return resolve('loaded');
|
||||
}
|
||||
catch (err) {
|
||||
CurrentPageisLoading = false;
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
// console.log('no target html '+targethtml);
|
||||
}
|
||||
|
||||
if (reqcacheload(targeturl)) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
$("#" + targetid).html(reqcacheload(targeturl));
|
||||
// $('#' + targetid).fadeIn();
|
||||
CurrentPageisLoading = false;
|
||||
return resolve('loaded');
|
||||
});
|
||||
}
|
||||
|
||||
CurrentPageisLoading = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Preloaders.PrecachePage(targeturl, targetid, () => {
|
||||
CurrentPageisLoading = false;
|
||||
resolve('loaded');
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
DropZoneFunc = {
|
||||
|
||||
AcceptedFilesString: {
|
||||
images: 'image/*',
|
||||
docs: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx',
|
||||
documents: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx'
|
||||
},
|
||||
|
||||
AddPreviouslyUploadedFiles: function (dropzoneobject, filesarray) {
|
||||
/* const previousFiles = [
|
||||
{
|
||||
name: "example.jpg",
|
||||
size: 12345,
|
||||
url: "/path/to/example.jpg",
|
||||
thumbnail: "/path/to/thumbnail/example.jpg"
|
||||
},
|
||||
// Add more files as needed
|
||||
];*/
|
||||
filesarray.forEach(file => {
|
||||
const mockFile = {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
url: file.url
|
||||
};
|
||||
|
||||
dropzoneobject.emit("addedfile", mockFile);
|
||||
dropzoneobject.emit("thumbnail", mockFile, file.thumbnail);
|
||||
dropzoneobject.emit("complete", mockFile);
|
||||
dropzoneobject.emit("url", mockFile, file.url, file.name);
|
||||
// Optionally, if you have server-side file handling, you may need to set this to true
|
||||
// dropzoneobject.emit("success", mockFile, file.serverResponse);
|
||||
});
|
||||
},
|
||||
|
||||
ReplaceDropzoneFiles: function (dropzoneid, newFilesArray) {
|
||||
const dropzoneobject = window.currentDropzone[dropzoneid];
|
||||
if (!dropzoneobject) {
|
||||
console.warn(`Dropzone '${dropzoneid}' not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
DropZoneFunc.ClearDropzoneUpload(dropzoneid, dropzoneobject);
|
||||
|
||||
|
||||
DropZoneFunc.AddPreviouslyUploadedFiles(dropzoneobject, newFilesArray);
|
||||
|
||||
|
||||
window.Target_Uploaded_Files = newFilesArray.map(f => f.hashkey || f.name);
|
||||
},
|
||||
|
||||
ResetDropzoneUpload: function (url, obj = null, reqtype = 'POST', successfunc = false) {
|
||||
|
||||
|
||||
function ReplaceDropZoneFilesfrom(filesarray) {
|
||||
DropZoneFunc.ClearDropzoneUpload();
|
||||
DropZoneFunc.AddPreviouslyUploadedFilesDropZone(myDropzone, filesarray);
|
||||
Target_Uploaded_Files = filesarray.map(file => file.hashkey);
|
||||
}
|
||||
if (!reqtype) { reqtype = 'POST'; }
|
||||
if (!obj) { obj = null; }
|
||||
let DRrequest = RequestData(false);
|
||||
DRrequest.url(url).data(obj).type(reqtype).fromVarCache(false).success(function (response) {
|
||||
ReplaceDropZoneFilesfrom(response);
|
||||
if (typeof successfunc === 'function') { successfunc(response); }
|
||||
}).go();
|
||||
|
||||
},
|
||||
|
||||
ClearDropzoneUpload: function (dropzonetargetid = '', dropzoneobject = '') {
|
||||
//if (myDropzone){myDropzone.removeAllFiles(true);}
|
||||
if (!dropzonetargetid) {
|
||||
|
||||
$('#' + dropzonetargetid + ' .dz-preview.dz-complete').remove();
|
||||
|
||||
console.log('#ClearUploadButton-' + dropzonetargetid);
|
||||
// $('#ClearUploadButton-' + dropzonetargetid).hide();
|
||||
} else {
|
||||
$('.dz-preview.dz-complete').remove();
|
||||
}
|
||||
|
||||
if (!dropzoneobject) { myDropzone.emit("reset") } else {
|
||||
dropzoneobject.emit("reset");
|
||||
}
|
||||
|
||||
//$('.dz-default.dz-message').show()
|
||||
Target_Uploaded_Files = [];
|
||||
},
|
||||
|
||||
hasOngoingUploads: function (dropzoneobject) {
|
||||
return dropzoneobject.getUploadingFiles().length > 0;
|
||||
},
|
||||
|
||||
|
||||
RemoveLastUploadedDropzone: function (dropzoneobject) {
|
||||
const uploadedFiles = dropzoneobject.getAcceptedFiles();
|
||||
if (uploadedFiles.length > 0) {
|
||||
const lastFile = uploadedFiles[uploadedFiles.length - 1];
|
||||
dropzoneobject.removeFile(lastFile);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
InitializeDropZone: function (url, successcallback, dropzoneid = 'myAwesomeDropZone', acceptedFiles, filename, maxfilesize = 100, updatedropzonestatusFUNC = '', errorcallback = false) {
|
||||
const dropzoneconfig = {
|
||||
paramName: filename,
|
||||
maxFilesize: maxfilesize,
|
||||
url: url,
|
||||
acceptedFiles: acceptedFiles || null,
|
||||
|
||||
init: function () {
|
||||
const dropzoneobject = this;
|
||||
let ongoingUploads = 0;
|
||||
|
||||
function updateUploadStatus() {
|
||||
if (typeof updatedropzonestatusFUNC === 'function') {
|
||||
updatedropzonestatusFUNC();
|
||||
}
|
||||
}
|
||||
|
||||
this.on("addedfile", function (file) {
|
||||
ongoingUploads++;
|
||||
updateUploadStatus();
|
||||
});
|
||||
|
||||
this.on("uploadprogress", function (file, progress) {
|
||||
// optional
|
||||
});
|
||||
|
||||
this.on("complete", function (file) {
|
||||
ongoingUploads--;
|
||||
updateUploadStatus();
|
||||
});
|
||||
|
||||
|
||||
this.on("url", function (file, url, name) {
|
||||
|
||||
// Make filename clickable
|
||||
file.previewElement.classList.add("dz-success");
|
||||
file.previewElement.querySelector("[data-dz-name]").innerHTML =
|
||||
`<a href="${url}" target="_blank">${name}</a>`;
|
||||
|
||||
// Use the real image as thumbnail if it’s an image
|
||||
// if (file.type.startsWith("image/")) {
|
||||
// file.previewElement.querySelector("img").src = response.url;
|
||||
// }
|
||||
|
||||
|
||||
// Preserve your existing success callback
|
||||
|
||||
});
|
||||
|
||||
this.on("success", function (file, response) {
|
||||
if (response && response.url) {
|
||||
// Make filename clickable
|
||||
file.previewElement.classList.add("dz-success");
|
||||
file.previewElement.querySelector("[data-dz-name]").innerHTML =
|
||||
`<a href="${response.url}" target="_blank">${response.name}</a>`;
|
||||
|
||||
// Use the real image as thumbnail if it’s an image
|
||||
// if (file.type.startsWith("image/")) {
|
||||
// file.previewElement.querySelector("img").src = response.url;
|
||||
// }
|
||||
}
|
||||
|
||||
// Preserve your existing success callback
|
||||
if (typeof successcallback === 'function') { successcallback(response, file); }
|
||||
});
|
||||
|
||||
// 🔴 catch network/server errors (like HTTP 500)
|
||||
this.on("error", function (file, errorMessage, xhr) {
|
||||
ongoingUploads--;
|
||||
updateUploadStatus();
|
||||
|
||||
console.warn("Dropzone upload error:", errorMessage);
|
||||
|
||||
// Make sure it visually shows as failed
|
||||
file.previewElement?.classList.add("dz-error");
|
||||
|
||||
const errorNode = file.previewElement?.querySelector("[data-dz-errormessage]");
|
||||
if (errorNode) {
|
||||
const msg =
|
||||
(xhr && xhr.responseText) ||
|
||||
errorMessage ||
|
||||
"Upload failed";
|
||||
errorNode.textContent = msg;
|
||||
}
|
||||
|
||||
// Optionally auto-remove failed files
|
||||
setTimeout(() => {
|
||||
if (file && this.files.includes(file)) this.removeFile(file);
|
||||
}, 2000);
|
||||
|
||||
// Run custom error callback if provided
|
||||
if (typeof errorcallback === "function") {
|
||||
errorcallback(file, errorMessage, xhr);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
success: function (file, response) {
|
||||
if (response && response.success) {
|
||||
if (typeof successcallback === "function") successcallback(response, file);
|
||||
} else {
|
||||
// Even if backend returned 200, force an error state
|
||||
const msg = response?.error || "Upload failed";
|
||||
file.previewElement?.classList.add("dz-error");
|
||||
|
||||
const errorNode = file.previewElement?.querySelector("[data-dz-errormessage]");
|
||||
if (errorNode) errorNode.textContent = msg;
|
||||
|
||||
this.emit("error", file, msg);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const myDropzone = new Dropzone(`#${dropzoneid}`, dropzoneconfig);
|
||||
return myDropzone;
|
||||
},
|
||||
|
||||
AddFiles: function (url = '/File/Upload', errorcallback = '', filename = 'file', maxfilesize = 100, acceptedFiles = 'image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx', dropzoneid = 'my-awesome-dropzone', dropzonemodal = 'Dropzone-Modal') {
|
||||
|
||||
|
||||
function successcallback(response, file) {
|
||||
// if (!response || isNumeric(response)) {
|
||||
// RemoveLastUploadedDropzone();
|
||||
// return false;
|
||||
// } else {
|
||||
// Target_Uploaded_Files.push(response);
|
||||
// }
|
||||
|
||||
if (response.success && response.hashkey) {
|
||||
Target_Uploaded_Files.push(response.hashkey);
|
||||
} else {
|
||||
RemoveLastUploadedDropzone();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!myDropzone) {
|
||||
DropZoneFunc.InitializeDropZone(url, successcallback, dropzoneid = 'myAwesomeDropZone', acceptedFiles, filename, maxfilesize = 100);
|
||||
}
|
||||
|
||||
if (view_transaction_reset_once) {
|
||||
DropZoneFunc.ResetDropzoneUpload();
|
||||
view_transaction_reset_once = 0
|
||||
}
|
||||
|
||||
if (!filename) { filename = 'file'; }
|
||||
|
||||
if (!dropzonemodal) { dropzonemodal = 'Dropzone-Modal'; }
|
||||
|
||||
if (ElementExists(dropzonemodal)) { showmodal(dropzonemodal); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function button(content, value = '', onclick = '', idtext = '', setclass = '', addtionaldata = '', img = '') {
|
||||
if (img) { img = imgiconuserdefault(img); } else { img = ''; }
|
||||
return '<button value="' + value + '" ' + addtionaldata + ' ' + img + ' id="' + idtext + '" class="' + setclass + '" onclick="' + onclick + '">' + content + '</button>';
|
||||
}
|
||||
|
||||
function buttonGOTOPage(text, targetpage, targetdata = 0, idtext = '', addonclick = '', setclass = '', addtionaldata = '', img = '') {
|
||||
if (!text || !targetpage) { return false; }
|
||||
const onclick = `gotoPage('${targetpage}','${targetdata}'); ` + addonclick;
|
||||
return button(text, text, onclick, idtext, setclass + ' btn btn-default', addtionaldata, img);
|
||||
}
|
||||
|
||||
function buttonwarning(content, value = '', onclick = '', block = '', idtext = '', addclass = '', addtionaldata = '', img = '') {
|
||||
if (block) { block = 'btn-block'; } else { block = ''; }
|
||||
return button(content, value, onclick, idtext, 'btn btn-warning ' + block + ' ' + addclass, addtionaldata, img);
|
||||
}
|
||||
|
||||
function buttondanger(content, value = '', onclick = '', block = '', idtext = '', addclass = '', addtionaldata = '', img = '') {
|
||||
if (block) { block = 'btn-block'; } else { block = ''; }
|
||||
return button(content, value, onclick, idtext, 'btn btn-danger ' + block + ' ' + addclass, addtionaldata, img);
|
||||
}
|
||||
function buttonprimary(content, value = '', onclick = '', block = '', idtext = '', addclass = '', addtionaldata = '', img = '') {
|
||||
if (block) { block = 'btn-block'; } else { block = ''; }
|
||||
return button(content, value, onclick, idtext, 'btn btn-primary ' + block + ' ' + addclass, addtionaldata, img);
|
||||
}
|
||||
|
||||
function textinput(idtext = '', setclass = '', required = '', placeholder = '', value = '') {
|
||||
if (required) { required = 'required'; } else { required = ''; }
|
||||
return '<input type="text" class="' + setclass + '" id="' + idtext + '" placeholder="' + placeholder + '" ' + required + ' value="' + value + '">';
|
||||
}
|
||||
|
||||
function textformcontrol(idtext = '', addclass = '', required = '', placeholder = '', value = '') {
|
||||
return textinput(idtext, 'form-control ' + addclass, required, placeholder, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function HomeMenuButtons(buttonicon, buttonText, buttonGo, buttonVariabletoPass = '', iconwidth = '', iconheight = '', buttonstyle = '', buttononclick = '', divclass = '', divid = '', buttonid = '', textclass = '') {
|
||||
// buttonvariabletopass is just currenttarget
|
||||
if (!buttonstyle) { buttonstyle = 'bg-transparent border-0 btn-block' }
|
||||
if (!divclass) { divclass = 'col-6'; }
|
||||
if (!buttonVariabletoPass) { buttonVariabletoPass = 0; }
|
||||
if (!buttonGo && buttononclick) {
|
||||
buttontoclick = buttononclick;
|
||||
} else if (buttonGo) {
|
||||
buttontoclick = `ButtonGo('` + buttonGo + `', '` + buttonVariabletoPass + `')`;
|
||||
} else {
|
||||
buttontoclick = '';
|
||||
}
|
||||
if (!iconwidth) { iconwidth = '20%'; }
|
||||
if (!iconheight) { iconheight = '40%'; }
|
||||
if (!textclass) { textclass = 'profile-username text-center'; }
|
||||
|
||||
|
||||
return `<div class="` + divclass + `" id="` + divid + `"><button id="` + buttonid + `" onclick="` + buttontoclick + `" class="bg-transparent border-0 btn-block" style="">
|
||||
<div class="card-body box-profile">
|
||||
<div class="text-center">
|
||||
<img class=" img-circle rounded mx-auto d-block" src="`+ buttonicon + `" width="` + iconwidth + `" height="` + iconheight + `" style="overflow:hidden;"></div>
|
||||
<h3 class="profile-username text-center">`+ buttonText + `</h3>
|
||||
<p class="text-muted text-center"></p>
|
||||
</div>
|
||||
</button></div>`;
|
||||
}
|
||||
function createCard(cardtitle = '', cardid = '', cardtools = '', cardbodyid = '', cardbodytext = '', cardbodyclassadd = '', maincardstyle = '', titlecardhide = false, maincardaddclass = '') {
|
||||
if (titlecardhide) { titlecardhide = 'display:none;'; } else { titlecardhide = ';' }
|
||||
if (!cardbodyid && cardid) { cardbodyid = 'card-body-' + cardid; }
|
||||
return `<div class="card ${maincardaddclass}" id="` + cardid + `" style="` + maincardstyle + `">
|
||||
<div id="cardheader-${cardid}" class="card-header ui-sortable-handle" style="cursor: move;` + titlecardhide + `">
|
||||
<h3 class="card-title" style="" id="card-title-${cardid}">` + cardtitle + `</h3>
|
||||
<div class="card-tools" id="card-tools-${cardid}">
|
||||
`+ cardtools + `
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body `+ cardbodyclassadd + `" id='` + cardbodyid + `'>
|
||||
`+ cardbodytext + `
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
function CreateCardSimple(title = '', text = '', id = '', style = '') {
|
||||
let titlehidden = false;
|
||||
if (!title) { titlehidden = true; }
|
||||
return createCard(title, id, '', '', text, '', style, titlehidden);
|
||||
}
|
||||
|
||||
|
||||
function CreateBalanceCard(titletext = '', maincardid = 'main-card-body', firstrowtext = 'Total Balance', firstrowvalueid = 'total-balance', firstrowvaluetext = '...', secondrowtext = '', secondrowvalueid = '', secodrowvaluetext = '', secondrowvisible = false, footerrowid = '', footerrowtext = '', thirdrowtext = '', thirdrowvalueid = '', thirdrowvaluetext = '', thirdrowvisible = false) {
|
||||
if (titletext === false || titletext === null) { titletext = 'Welcome!'; }
|
||||
if (!secondrowvisible) { secondrowvisible = 'display:none;'; } else {
|
||||
secondrowvisible = '';
|
||||
}
|
||||
let thirdrowhtml = '';
|
||||
if (thirdrowtext || thirdrowvalueid || thirdrowvaluetext || thirdrowvisible) {
|
||||
if (thirdrowvisible) { thirdrowvisible = ''; } else { thirdrowvisible = 'display:none;'; }
|
||||
thirdrowhtml = `
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
`+ thirdrowtext + `
|
||||
</div>
|
||||
<div class="col text-right" id="`+ thirdrowvalueid + `">
|
||||
`+ thirdrowvaluetext + `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
return `<div class="card-body card-info" id="` + maincardid + `" style="">
|
||||
<div class="row">
|
||||
<div class="col-md-18">
|
||||
`+ titletext + `
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="row card-info">
|
||||
<div class="col-md-18 card text-xl border-rounded balancecard" style="width:100%;height:30%;">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
`+ firstrowtext + `
|
||||
</div>
|
||||
<div class="col text-right" id="`+ firstrowvalueid + `">
|
||||
`+ firstrowvaluetext + `
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="`+ secondrowvisible + `">
|
||||
<div class="col">
|
||||
`+ secondrowtext + `
|
||||
</div>
|
||||
<div class="col text-right" id="`+ secondrowvalueid + `">
|
||||
`+ secodrowvaluetext + `
|
||||
</div>
|
||||
</div>`
|
||||
+ thirdrowhtml +
|
||||
`<br><br>
|
||||
<div class="row ">
|
||||
<div class="col text-sm" id="`+ footerrowid + `">
|
||||
`+ footerrowtext + `
|
||||
</div>
|
||||
<div class="col">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function clearCacheAndReload(redirectTo = '/') {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
for (let registration of registrations) {
|
||||
logDev('Unregistering Old Service Worker');
|
||||
registration.unregister();
|
||||
|
||||
}
|
||||
caches.keys().then(cacheNames => {
|
||||
return Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
logDev('Deleting Old Cache');
|
||||
return caches.delete(cacheName);
|
||||
})
|
||||
);
|
||||
}).then(() => {
|
||||
|
||||
caches.keys().then(function (names) {
|
||||
for (let name of names) {
|
||||
caches.delete(name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (redirectTo !== null && redirectTo !== false) {
|
||||
window.location.href = redirectTo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
caches.keys().then(function (names) {
|
||||
for (let name of names) {
|
||||
caches.delete(name);
|
||||
}
|
||||
});
|
||||
|
||||
if (redirectTo !== null && redirectTo !== false) {
|
||||
window.location.href = redirectTo;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function logoutnow() {
|
||||
window.location.href = '/go/logoutnow';
|
||||
}
|
||||
|
||||
function clearCacheAndLogout() {
|
||||
clearCacheAndReload('/go/logoutnow');
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ResetBrowserAndCache() {
|
||||
logDev('Resetting Browser Cache');
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistrations().then(registrations => {
|
||||
for (let registration of registrations) {
|
||||
registration.unregister();
|
||||
}
|
||||
window.location.reload(true);
|
||||
});
|
||||
} else {
|
||||
console.warn('Service workers are not supported in this browser.');
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
function GenerateTheadFromArraySimple(array) {
|
||||
html = `<tr>`;
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach((item, index) => {
|
||||
html += `<th>` + item + `</th>`;
|
||||
});
|
||||
} else if (array !== null && typeof array === 'object') {
|
||||
Object.entries(array).forEach(([key, value]) => {
|
||||
html += `<th>` + value + `</th>`;
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
html += `</tr>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
function GenerateTableRowFromArraySimple(array) {
|
||||
html = `<tr>`;
|
||||
if (Array.isArray(array)) {
|
||||
array.forEach((item, index) => {
|
||||
html += `<td>` + item + `</td>`;
|
||||
});
|
||||
} else if (array !== null && typeof array === 'object') {
|
||||
Object.entries(array).forEach(([key, value]) => {
|
||||
html += `<td>` + value + `</td>`;
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
html += `</tr>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
function ArraytoDatalist(id, array, replace = true) {
|
||||
if (!id || !array) { return false; }
|
||||
|
||||
if (replace) {
|
||||
const datalist = document.getElementById(id);
|
||||
if (!datalist) { return false; }
|
||||
datalist.innerHTML = "";
|
||||
|
||||
array.forEach(item => {
|
||||
const option = document.createElement('option');
|
||||
option.value = item;
|
||||
datalist.appendChild(option);
|
||||
});
|
||||
return datalist;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!replace) {
|
||||
let htmlstring = '<datalist id="' + id + '">';
|
||||
array.forEach(item => {
|
||||
htmlstring += '<option value="' + item + '">';
|
||||
});
|
||||
htmlstring += '</datalist>';
|
||||
return htmlstring;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function QueryandReplaceDatalist(datalistid, url, method = 'POST', datatosend = null, fromvarcache = false, successfunc = false, errorfunc = false) {
|
||||
if (!document.getElementById(datalistid) || !url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let RequestDataDatalist = new RequestData(false);
|
||||
RequestDataDatalist.url(url).type(method).fromVarCache(fromvarcache).data(datatosend)
|
||||
.success((response) => {
|
||||
|
||||
if (typeof successfunc === 'function') {
|
||||
successfunc(response);
|
||||
}
|
||||
|
||||
|
||||
ArraytoDatalist(datalistid, response, replace = true);
|
||||
|
||||
}).error((response) => {
|
||||
if (errorfunc && typeof errorfunc === 'function') {
|
||||
errorfunc(response);
|
||||
}
|
||||
|
||||
}).go();;
|
||||
|
||||
}
|
||||
|
||||
function ReqCachetoDatalist(url, id, replace = true) {
|
||||
const data = reqcacheload(url, datavalue = '', object = '');
|
||||
if (!data) { return false; }
|
||||
|
||||
return ArraytoDatalist(id, data, replace);
|
||||
}
|
||||
|
||||
|
||||
function CreateTable(id, theadinnerhtml, bodyinnerhtml) {
|
||||
return `<table id="` + id + `">
|
||||
<thead>
|
||||
`+ theadinnerhtml + `
|
||||
</thead>
|
||||
<tbody>
|
||||
`+ bodyinnerhtml + `
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
|
||||
function LoadPhotosCard(photosdivid, onclick = '', photosarray = '', photoscardid = 'PhotosCard', maxwidth = '300px', maxheight = '300px', preloadFileBlobURL = false) {
|
||||
// onclick sample ButtonGo('ViewAllPhotos','${currenttarget}');
|
||||
|
||||
if (!maxwidth && !maxheight) { maxwidth = '300px'; maxheight = '300px'; }
|
||||
let photosdiv = $('#' + photosdivid);
|
||||
|
||||
if (photosdiv.length === 0) { return false; }
|
||||
if (!photosarray) { photosdiv.html('No Photos.<br>'); return false; }
|
||||
|
||||
if (photosarray && typeof photosarray === 'string') {
|
||||
try {
|
||||
photolist = JSON.parse(photosarray);
|
||||
} catch (error) {
|
||||
photolist = [photosarray];
|
||||
}
|
||||
} else {
|
||||
photolist = photosarray;
|
||||
}
|
||||
|
||||
if (preloadFileBlobURL) {
|
||||
photolist.forEach(function (photo) {
|
||||
Preloaders.getfileBlobURL(photo);
|
||||
});
|
||||
}
|
||||
|
||||
if (!photolist || photolist.length === 0) { photosdiv.html('No Photo.<br>'); return false; }
|
||||
let htmlbody = $(`<div class="splide" role="group" aria-label="photosSplide">
|
||||
<div class="splide__track">
|
||||
<ul class="splide__list">
|
||||
</ul> </div></div>`);
|
||||
const NewSplideLIImage = function (imgsrc) {
|
||||
return `<li class="splide__slide" onclick="${onclick}"><img src="${imgsrc}" style="max-width: ${maxwidth};
|
||||
max-height: ${maxheight};
|
||||
width: auto;
|
||||
height: auto;"></li>`;
|
||||
};
|
||||
let splidebody = htmlbody.find('ul');
|
||||
LoadDataPageFunc.PhotoBlobs = [];
|
||||
photolist.forEach(function (photo) {
|
||||
LoadAndCreateURLfromFileHash(photo).then(bloburl => {
|
||||
splidebody.append(NewSplideLIImage(bloburl));
|
||||
|
||||
if (typeof LoadDataPageFunc.PhotoBlobs === 'undefined') {
|
||||
LoadDataPageFunc.PhotoBlobs = [];
|
||||
}
|
||||
LoadDataPageFunc.PhotoBlobs.push(bloburl);
|
||||
});
|
||||
});
|
||||
|
||||
photosdiv.html(htmlbody);
|
||||
new Splide('.splide').mount();
|
||||
};
|
||||
|
||||
|
||||
function LoadPhototoIMG(photimgid, photohash, onclick = '', maxwidth = '300px', maxheight = '300px') {
|
||||
if (typeof photohash === 'array') {
|
||||
photohash = photohash[0];
|
||||
}
|
||||
LoadAndCreateURLfromFileHash(photohash).then(bloburl => {
|
||||
$('#' + photimgid).attr('src', bloburl);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function LoadPhotoIMGTargetClass(imgclassname, settodisplaylater = true) {
|
||||
if (!imgclassname) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const images = document.querySelectorAll(`img.${imgclassname}`);
|
||||
|
||||
images.forEach((img) => {
|
||||
const photohash = img.getAttribute('src');
|
||||
|
||||
LoadAndCreateURLfromFileHash(photohash).then(bloburl => {
|
||||
img.setAttribute('src', bloburl);
|
||||
|
||||
if (settodisplaylater) {
|
||||
img.style.display = 'block';
|
||||
}
|
||||
}).catch(error => {
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function HideBrokenPhotowithClass(imgclassname) {
|
||||
if (!imgclassname) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const images = document.querySelectorAll(`img.${imgclassname}`);
|
||||
images.forEach((img) => {
|
||||
img.addEventListener('error', () => {
|
||||
img.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates Input Form
|
||||
* @param {Array} ArrayOfRequiredInput - Array of required input field IDs
|
||||
* @returns {boolean|Array} - Returns an array of values if all required fields are filled, false otherwise
|
||||
*/
|
||||
function validateInputForm(ArrayOfRequiredInput) {
|
||||
const inputsArray = getInputElementsValuesObjectbyCSSClassname(LoadDataPageFunc.formclass);
|
||||
if (isObjectEmpty(inputsArray)) {
|
||||
return false;
|
||||
}
|
||||
let values = [];
|
||||
for (let id of ArrayOfRequiredInput) {
|
||||
const value = inputsArray[id];
|
||||
if (value === undefined || value === null || String(value).trim() === "") {
|
||||
return false;
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
71
resources/js/utils/memoize.js
Normal file
71
resources/js/utils/memoize.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
/**
|
||||
* Vue 3 composable for memoizing functions.
|
||||
* Works across components and SPA navigation (module-level singleton cache).
|
||||
*
|
||||
* Usage:
|
||||
* const { memoize, memoizeFull } = useMemoize()
|
||||
* const add10 = (n) => n + 10
|
||||
* const cachedAdd = memoize(add10)
|
||||
* cachedAdd(5) // calculates
|
||||
* cachedAdd(5) // returns cached result
|
||||
*/
|
||||
export function useMemoize() {
|
||||
// reactive singleton caches
|
||||
const cacheSingle = reactive({})
|
||||
const cacheMulti = reactive({})
|
||||
|
||||
/**
|
||||
* Memoize a single-argument function.
|
||||
* @param {Function} fn - Function with one argument
|
||||
* @returns {Function} Memoized function
|
||||
*/
|
||||
function memoize(fn) {
|
||||
return (arg) => {
|
||||
if (arg in cacheSingle) {
|
||||
// console.log('Fetching from cache')
|
||||
return cacheSingle[arg]
|
||||
}
|
||||
// console.log('Calculating result')
|
||||
const result = fn(arg)
|
||||
cacheSingle[arg] = result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoize a function with multiple arguments.
|
||||
* @param {Function} fn - Function with any number of arguments
|
||||
* @returns {Function} Memoized function
|
||||
*/
|
||||
function memoizeFull(fn) {
|
||||
return (...args) => {
|
||||
const key = JSON.stringify(args)
|
||||
if (cacheMulti[key]) {
|
||||
// console.log('Fetching from cache for:', args)
|
||||
return cacheMulti[key]
|
||||
}
|
||||
|
||||
const result = fn(...args)
|
||||
cacheMulti[key] = result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional: clear caches
|
||||
*/
|
||||
function clearCache() {
|
||||
Object.keys(cacheSingle).forEach(k => delete cacheSingle[k])
|
||||
Object.keys(cacheMulti).forEach(k => delete cacheMulti[k])
|
||||
}
|
||||
|
||||
return {
|
||||
memoize,
|
||||
memoizeFull,
|
||||
cacheSingle,
|
||||
cacheMulti,
|
||||
clearCache
|
||||
}
|
||||
}
|
||||
29
resources/js/utils/mutationObserver.js
Normal file
29
resources/js/utils/mutationObserver.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { onMounted, onBeforeUnmount } from 'vue'
|
||||
|
||||
/**
|
||||
* Watch for DOM changes on a target element and run a callback.
|
||||
*
|
||||
* @param {HTMLElement | null} targetElement - Element to monitor
|
||||
* @param {Function} callback - Function to run on mutations
|
||||
*/
|
||||
export function useMutationObserver(targetElement, callback) {
|
||||
let observer = null
|
||||
|
||||
onMounted(() => {
|
||||
if (!targetElement) return
|
||||
|
||||
observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
observer.observe(targetElement, { childList: true, subtree: true })
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (observer) observer.disconnect()
|
||||
})
|
||||
}
|
||||
50
resources/js/utils/navigate.js
Normal file
50
resources/js/utils/navigate.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import axios from 'axios';
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
const navigate = async ({ page }) => {
|
||||
try {
|
||||
// Show loading spinner if you want
|
||||
loading.value = true;
|
||||
|
||||
// Request page data from server
|
||||
const response = await axios.get(`/${page}`);
|
||||
const data = response.data;
|
||||
|
||||
// Expected server response:
|
||||
// { component: 'Home', props: {...} }
|
||||
currentPage.value = data.component;
|
||||
currentProps.value = data.props || {};
|
||||
} catch (error) {
|
||||
console.error('Navigation error', error);
|
||||
currentPage.value = 'NotFound';
|
||||
currentProps.value = {};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reloads the current SPA page
|
||||
* without changing history.
|
||||
*
|
||||
* Vue replacement for legacy ReloadPage()
|
||||
*/
|
||||
export function reloadPage() {
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
const navigate =
|
||||
instance?.proxy?.$navigate ||
|
||||
window.$navigate
|
||||
|
||||
if (!navigate) {
|
||||
console.warn('No SPA navigator available')
|
||||
return
|
||||
}
|
||||
|
||||
navigate({
|
||||
page: instance.proxy.currentPage,
|
||||
props: instance.proxy.currentProps,
|
||||
nohistory: true,
|
||||
redundantpage: true
|
||||
})
|
||||
}
|
||||
41
resources/js/utils/notifications.js
Normal file
41
resources/js/utils/notifications.js
Normal file
@@ -0,0 +1,41 @@
|
||||
export function setNotif(title, body = '', icon = '', tag = '') {
|
||||
Notification.requestPermission(result => {
|
||||
if (result === 'granted') {
|
||||
navigator.serviceWorker.ready.then(reg =>
|
||||
reg.showNotification(title, { body, icon, tag })
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function eraseAllNotif() {
|
||||
Notification.requestPermission(result => {
|
||||
if (result === 'granted') {
|
||||
navigator.serviceWorker.ready.then(reg =>
|
||||
reg.getNotifications().then(n => n.forEach(x => x.close()))
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function EraseNotifwithTag(tag) {
|
||||
Notification.requestPermission(function (result) {
|
||||
if (result === 'granted') {
|
||||
// Replace 'specific-tag' with the tag you want to target
|
||||
const specificTag = tag;
|
||||
|
||||
// Check if service workers are available
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(function (registration) {
|
||||
registration.getNotifications().then(function (notifications) {
|
||||
notifications.forEach(function (notification) {
|
||||
if (notification.tag === specificTag) {
|
||||
notification.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
32
resources/js/utils/object.js
Normal file
32
resources/js/utils/object.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Checks whether an object has only empty values.
|
||||
*
|
||||
* Empty values:
|
||||
* - undefined
|
||||
* - null
|
||||
* - empty string
|
||||
* - empty array
|
||||
* - empty object
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @example
|
||||
* isObjectEmpty({}) // true
|
||||
* isObjectEmpty({ a: '' }) // true
|
||||
* isObjectEmpty({ a: [] }) // true
|
||||
* isObjectEmpty({ a: {} }) // true
|
||||
* isObjectEmpty({ a: 1 }) // false
|
||||
*/
|
||||
export function isObjectEmpty(obj) {
|
||||
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Object.values(obj).every(value => {
|
||||
if (value === undefined || value === null || value === '') return true
|
||||
if (Array.isArray(value)) return value.length === 0
|
||||
if (typeof value === 'object') return Object.keys(value).length === 0
|
||||
return false
|
||||
})
|
||||
}
|
||||
42
resources/js/utils/random.js
Normal file
42
resources/js/utils/random.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Generates a unique random hash string.
|
||||
*
|
||||
* Uses `window.crypto` if available, otherwise falls back to Math.random.
|
||||
*
|
||||
* @param {number} [length=16] - Desired length of the hash
|
||||
* @returns {string}
|
||||
*
|
||||
* @example
|
||||
* createUniqueRandomHash(8) // "3f4a1b9c"
|
||||
*/
|
||||
export function createUniqueRandomHash(length = 16) {
|
||||
if (window.crypto && window.crypto.getRandomValues) {
|
||||
const array = new Uint8Array(length)
|
||||
window.crypto.getRandomValues(array)
|
||||
return Array.from(array)
|
||||
.map(byte => (byte % 16).toString(16))
|
||||
.join('')
|
||||
}
|
||||
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
let hash = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
hash += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generates a short unique key string.
|
||||
* Uses `Math.random()` and base36 conversion.
|
||||
*
|
||||
* @returns {string} - A random alphanumeric string (length ~9)
|
||||
*
|
||||
* @example
|
||||
* const key = generateUniqueKey() // e.g., "5gk8q2m1a"
|
||||
*/
|
||||
export function generateUniqueKey() {
|
||||
return Math.random().toString(36).substring(2, 11)
|
||||
}
|
||||
59
resources/js/utils/uiHelpers.js
Normal file
59
resources/js/utils/uiHelpers.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* UI Helper Utilities
|
||||
* Extracted from Legacy/UIALT.js — pure functions that don't map to Vue components.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get values of all input/textarea elements by CSS class name.
|
||||
* Returns an array of { id, value } objects.
|
||||
*
|
||||
* @param {string} className
|
||||
* @returns {{ id: string, value: string }[]}
|
||||
*/
|
||||
export function getInputAndTextareaValuesByClassName(className) {
|
||||
const elements = document.getElementsByClassName(className)
|
||||
const results = []
|
||||
Array.from(elements).forEach(element => {
|
||||
results.push({ id: element.id, value: element.value })
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Get values of all input elements by CSS class name.
|
||||
* Returns an object keyed by element id.
|
||||
*
|
||||
* @param {string} className
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
export function getInputElementsValuesObjectByClassName(className) {
|
||||
const elements = document.getElementsByClassName(className)
|
||||
const results = {}
|
||||
Array.from(elements).forEach(element => {
|
||||
results[element.id] = element.value
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject dynamic CSS into a <style> tag with id="dynamic-css".
|
||||
* Creates the tag if it doesn't exist.
|
||||
*
|
||||
* @param {string} cssText
|
||||
*/
|
||||
export function setDynamicCSS(cssText) {
|
||||
let styleTag = document.getElementById('dynamic-css')
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.id = 'dynamic-css'
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
styleTag.innerHTML = cssText
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all dynamic CSS.
|
||||
*/
|
||||
export function resetDynamicCSS() {
|
||||
setDynamicCSS('')
|
||||
}
|
||||
63
resources/js/utils/userTypeLabels.js
Normal file
63
resources/js/utils/userTypeLabels.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useUIStore } from '../stores/ui'
|
||||
|
||||
const CORPORATE_LABELS = {
|
||||
'ult': 'Ultimate',
|
||||
'super operator': 'Super Operator',
|
||||
'operator': 'Operator',
|
||||
'coordinator': 'Coordinator',
|
||||
'supplier overseer': 'Supplier Overseer',
|
||||
'wholesale buyer': 'Wholesale Buyer',
|
||||
'supplier': 'Supplier',
|
||||
'store owner': 'Store Owner',
|
||||
'store manager': 'Store Manager',
|
||||
'user': 'User',
|
||||
'rider': 'Rider',
|
||||
'audit': 'Audit',
|
||||
'pos terminal': 'POS Terminal',
|
||||
}
|
||||
|
||||
const COOPERATIVE_LABELS = {
|
||||
'ult': 'Chairperson',
|
||||
'super operator': 'General Manager',
|
||||
'operator': 'Officer',
|
||||
'coordinator': 'Chapter Coordinator',
|
||||
'supplier overseer': 'Supplier Liaison',
|
||||
'wholesale buyer': 'Bulk Buyer',
|
||||
'supplier': 'Member-Supplier',
|
||||
'store owner': 'Branch Owner',
|
||||
'store manager': 'Branch Manager',
|
||||
'user': 'Member',
|
||||
'rider': 'Rider',
|
||||
'audit': 'Auditor',
|
||||
'pos terminal': 'POS Terminal',
|
||||
}
|
||||
|
||||
const TYPE_BADGE_CLASS = {
|
||||
'ult': 'bg-danger',
|
||||
'super operator': 'bg-warning text-dark',
|
||||
'operator': 'bg-primary',
|
||||
'coordinator': 'bg-info text-dark',
|
||||
'store owner': 'bg-success',
|
||||
'store manager': 'bg-info text-white',
|
||||
'user': 'bg-secondary',
|
||||
}
|
||||
|
||||
export function getUserTypeLabel(type, mode) {
|
||||
if (!type) return 'Unknown'
|
||||
const key = String(type).toLowerCase()
|
||||
const map = mode === 'cooperative' ? COOPERATIVE_LABELS : CORPORATE_LABELS
|
||||
return map[key] || CORPORATE_LABELS[key] || (key.charAt(0).toUpperCase() + key.slice(1))
|
||||
}
|
||||
|
||||
export function getUserTypeBadgeClass(type) {
|
||||
if (!type) return 'bg-secondary'
|
||||
return TYPE_BADGE_CLASS[String(type).toLowerCase()] || 'bg-secondary'
|
||||
}
|
||||
|
||||
export function useUserTypeLabels() {
|
||||
const ui = useUIStore()
|
||||
return {
|
||||
label: (type) => getUserTypeLabel(type, ui.app_mode),
|
||||
badgeClass: (type) => getUserTypeBadgeClass(type),
|
||||
}
|
||||
}
|
||||
79
resources/js/utils/validators.js
Normal file
79
resources/js/utils/validators.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Checks if an email address is valid.
|
||||
*
|
||||
* @param {string} email
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @example
|
||||
* isValidEmail('test@example.com') // true
|
||||
*/
|
||||
export function isValidEmail(email = '') {
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailPattern.test(email)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates Philippine mobile number format.
|
||||
*
|
||||
* Must start with 09 and contain exactly 11 digits.
|
||||
*
|
||||
* @param {string} mobile
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @example
|
||||
* hasValidMobileFormat('09171234567') // true
|
||||
*/
|
||||
export function hasValidMobileFormat(mobile = '') {
|
||||
const pattern = /^09\d{9}$/
|
||||
return pattern.test(mobile)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a value is numeric.
|
||||
*
|
||||
* Accepts strings or numbers.
|
||||
* Rejects empty strings, whitespace, and NaN.
|
||||
*
|
||||
* @param {string|number} value
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @example
|
||||
* isNumeric('123') // true
|
||||
* isNumeric(45) // true
|
||||
* isNumeric('12.3') // true
|
||||
* isNumeric('') // false
|
||||
* isNumeric(' ') // false
|
||||
*/
|
||||
export function isNumeric(value) {
|
||||
if (value === null || value === undefined) return false
|
||||
if (typeof value === 'string' && value.trim() === '') return false
|
||||
return !Number.isNaN(Number(value))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a response is a "hash-like" string.
|
||||
*
|
||||
* A valid hash is:
|
||||
* - not empty
|
||||
* - not `true`
|
||||
* - not numeric
|
||||
* - does not contain spaces
|
||||
*
|
||||
* @param {*} response - The value to check
|
||||
* @returns {string|false} - Returns the string if it is a hash, otherwise false
|
||||
*
|
||||
* @example
|
||||
* isResponseAHash('abc123') // 'abc123'
|
||||
* isResponseAHash('123') // false
|
||||
* isResponseAHash(true) // false
|
||||
* isResponseAHash('hello world') // false
|
||||
*/
|
||||
export function isResponseAHash(response) {
|
||||
if (!response || response === true) return false
|
||||
|
||||
const result = String(response)
|
||||
if (result.includes(' ') || !isNaN(result)) return false
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user