{"id":9392,"date":"2025-03-31T13:00:56","date_gmt":"2025-03-31T12:00:56","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=9392"},"modified":"2025-05-23T10:48:19","modified_gmt":"2025-05-23T09:48:19","slug":"typescript-helpers-tshelpers","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/typescript-helpers-tshelpers\/","title":{"rendered":"Typescript helpers (tshelpers)"},"content":{"rendered":"\n<p>Iterables \/ Arrays<\/p>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">type IterableKind&lt;T> = Iterable&lt;T> | T[];\n\nexport function ensureArray&lt;T>(data?: IterableKind&lt;T>): T[] {\n    if (!data) return [];\n    if (Array.isArray(data)) return data;\n    if (typeof data === \"string\") return [data];\n    if (data instanceof Map) return [];\n    return Array.from(data);\n}\n\nexport function firstItem&lt;T>(data?: IterableKind&lt;T>): T | undefined {\n    return ensureArray(data)[0];\n};\n\nexport function arraySum&lt;T>(arr: IterableKind&lt;T>, fn: (arg: T) => number): number {\n    return ensureArray(arr).reduce((p, c) => p + fn(c), 0);\n}\n\nexport function arrayMin&lt;T>(arr: IterableKind&lt;T>, fn: (arg: T) => number): number | undefined {\n    const values = ensureArray(arr).map(fn);\n    return values.length ? Math.min.apply(Math, values) : undefined;\n}\n\nexport function arrayMax&lt;T>(arr: IterableKind&lt;T>, fn: (arg: T) => number): number | undefined {\n    const values = ensureArray(arr).map(fn);\n    return values.length ? Math.max.apply(Math, values) : undefined;\n}<\/pre><\/div>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">import { getISOWeek, getISOWeekYear } from \"date-fns\";\nimport { IYearWeek } from \"..\/domain\/general\/IYearWeek\";\n\nexport const isDebugMode = `${window.location.origin}`.includes(\"localhost\");\n\nconst eq = (a: string, b: string) => a === b;\n\nconst strEq = (a?: string, b?: string) => String(a).trim().toLowerCase() === String(b).trim().toLowerCase();\n\nconst remove = (arr: any[], item: any) => {\n    const i = arr.indexOf(item);\n    if (i >= 0) { arr.splice(i, 1); }\n};\nconst isset = (x: any) => ![undefined, null].includes(x);\n\nconst getString = (x: any) => isset(x) ? `${x}` : \"\";\n\nconst getFirstItem = (obj: any) => {\n    if (isset(obj)) {\n        return Array.from(obj)[0];\n    }\n    return null;\n};\n\n\nexport type Dictionary&lt;T> = { [id: string]: T };\n\nconst htmlEncode = (source: string) => {\n    let i = 0;\n    let ch: string;\n    let peek = \"\";\n    const result: string[] = [];\n    let line: string[] = [];\n    \/\/ Stash the next character and advance the pointer\n    const next = () => {\n        peek = source.charAt(i);\n        i += 1;\n    };\n    \/\/ Start a new \"line\" of output, to be joined later by &lt;br \/>\n    const endline = () => {\n        result.push(line.join(\"\"));\n        line = [];\n    };\n    \/\/ Push a character or its entity onto the current line\n    const push = () => {\n        if (ch !== \"\\r\" &amp;&amp; ch !== \"\\n\" &amp;&amp; (ch &lt; \" \" || ch > \"~\")) {\n            line.push(`&amp;#${ch.charCodeAt(0)};`);\n        } else {\n            line.push(ch);\n        }\n    };\n    next();\n    while (i &lt;= source.length) {\n        ch = peek;\n        next();\n        switch (ch) {\n            case \"&lt;\":\n                line.push(\"&lt;\");\n                break;\n            case \">\":\n                line.push(\">\");\n                break;\n            case \"&amp;\":\n                line.push(\"&amp;\");\n                break;\n            case \"\\\"\":\n                line.push(\"\"\");\n                break;\n            case \"'\":\n                line.push(\"'\");\n                break;\n            default:\n                push();\n        }\n    }\n    endline();\n    return result.join(\"&lt;br \/>\");\n};\n\nconst unique = (arr: any[], valFn: (item: any) => any) => {\n    const values: { [key: string]: any } = {};\n    return arr.filter(item => {\n        const val = String(valFn(item));\n        return val in values ? false : values[val] = true;\n    });\n};\n\nconst propertiesEqual = (a: any, b: any, properties: string[]) => a &amp;&amp; b &amp;&amp; properties.every((p: any) => a[p] === b[p]);\n\nexport function arraysEqual(arr1: any[], arr2: any[]) {\n    return arr1.length === arr2.length &amp;&amp; arr1.every((val, index) => val === arr2[index]);\n}\nexport function arraysEqualUnordered(arr1: any[], arr2: any[]) {\n    return JSON.stringify([...arr1].sort()) === JSON.stringify([...arr2].sort());\n}\n\n\nconst dateAdd = (date: Date, ms: number) => new Date(date.getTime() + ms);\nconst getToday = () => {\n    const d = new Date();\n    d.setHours(23, 59, 59);\n    return d;\n};\n\nexport interface IPageInfo { from?: Date; to?: Date; page: number; }\ntype Pager = (page: number) => IPageInfo;\n\nconst historyPager = (msRange: number): Pager => {\n    const today = getToday();\n    return (page: number) => {\n        const from = dateAdd(today, -msRange * (page + 1));\n        from.setHours(0, 0, 0);\n        let to: Date | undefined = dateAdd(today, -msRange * page);\n        if (page &lt; 1) {\n            to = undefined;\n        }\n        return {\n            from,\n            to,\n            page\n        };\n    };\n};\n\n\/\/ only skips sunday\nconst nextWorkDay = function (date: Date): Date {\n    const tomorrow = new Date(date.setDate(date.getDate() + 1));\n    return tomorrow.getDay() % 7 ? tomorrow : nextWorkDay(tomorrow);\n};\n\n\/\/ only skips sunday\nconst lastWorkday = function (date: Date): Date {\n    const yesterday = new Date(date.setDate(date.getDate() - 1));\n    return yesterday.getDay() % 7 ? yesterday : lastWorkday(yesterday);\n};\n\nconst isFileImage = (fileName: string) => {\n    const extensions = [\".JPG\", \".JPEG\", \".JPE\", \".BMP\", \".GIF\", \".PNG\"];\n    return extensions.some(x => fileName.toUpperCase().includes(x));\n};\n\nconst isValidDate = (d: any) => d instanceof Date &amp;&amp; !isNaN(d.getTime());\n\nconst getNumber = (x: any) => Number(String(x).replace(\/([^\\d])\/, \"\").replace(\/^0+\/, \"\"));\n\nconst isNumberKey = (event: any) => {\n    const charCode = (event.which) ? event.which : event.keyCode;\n    if (charCode !== 46 &amp;&amp; charCode > 31 &amp;&amp; (charCode &lt; 48 || charCode > 57)) {\n        return false;\n    }\n    return true;\n};\n\nconst escapeHtml = (unsafe: any): string => {\n    return String(unsafe)\n        .replace(\/&amp;\/g, \"&amp;\")\n        .replace(\/&lt;\/g, \"&lt;\")\n        .replace(\/>\/g, \">\")\n        .replace(\/\"\/g, \"\"\")\n        .replace(\/'\/g, \"'\");\n};\n\nconst onChange = (objToWatch: any, fn: any) => {\n    const handler = {\n        set(target: any, property: any, value: any) {\n            fn();\n            return Reflect.set(target, property, value);\n        },\n        deleteProperty(target: any, property: any) {\n            fn();\n            return Reflect.deleteProperty(target, property);\n        }\n    }; return new Proxy(objToWatch, handler);\n};\n\nfunction debounced(delay: number, fn: (...args: Array&lt;any>) => any) {\n    let timerId: any;\n    return function (...args: Array&lt;any>) {\n        if (timerId) {\n            clearTimeout(timerId);\n        }\n        timerId = setTimeout(() => {\n            fn(...args);\n            timerId = null;\n        }, delay);\n    };\n}\n\nconst displayTextAreaProperly = (str: string): string => {\n    if (str) {\n        return str.replace(\/\\n\/g, \"&lt;br>\");\n    }\n    return \"\";\n};\n\nconst createCounter = () => {\n    let c = 0;\n    return () => { c += 1; return c; };\n};\n\nconst getCircularReplacer = () => {\n    const seen = new WeakSet();\n    return (_key: any, value: any) => {\n        if (typeof value === \"object\" &amp;&amp; value !== null) {\n            if (seen.has(value)) {\n                return;\n            }\n            seen.add(value);\n        }\n        return value;\n    };\n};\nconst toJson = (x: any) => JSON.stringify(x, getCircularReplacer());\nconst parseJson = (x: any) => {\n    if (typeof x !== \"string\") { return undefined; }\n    try {\n        return JSON.parse(x);\n    } catch {\n        return undefined;\n    }\n};\nconst getLocalStorage = &lt;T>(key: string): T => parseJson(localStorage.getItem(key)) as T;\nconst setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state));\n\nexport const getSessionStorage = &lt;T>(key: string): T => parseJson(sessionStorage.getItem(key)) as T;\nexport const setSessionStorage = (key: string, state: any): any => sessionStorage.setItem(key, toJson(state));\n\nconst c = encodeURIComponent;\nconst cv = (v: any) => v &amp;&amp; v.toISOString ? v.toISOString() : v;\nconst objToQueryString = (obj: any) => Object.keys(obj).map(k => `${c(k)}=${c(cv(obj[k]))}`).join(\"&amp;\");\n\nfunction b64toBlob(b64Data: string, contentType: string, sliceSize?: number): Blob {\n    contentType = contentType || \"\";\n    sliceSize = sliceSize || 512;\n    const byteCharacters = atob(b64Data);\n    const byteArrays = [];\n\n    for (let offset = 0; offset &lt; byteCharacters.length; offset += sliceSize) {\n        const slice = byteCharacters.slice(offset, offset + sliceSize);\n        const byteNumbers = new Array(slice.length);\n        for (let i = 0; i &lt; slice.length; i++) {\n            byteNumbers[i] = slice.charCodeAt(i);\n        }\n        const byteArray = new Uint8Array(byteNumbers);\n        byteArrays.push(byteArray);\n    }\n    return new Blob(byteArrays, { type: contentType });\n}\n\nfunction imagedataToBlob(imagedata: ImageData): Promise&lt;Blob> {\n    return new Promise((resolve, reject) => {\n        try {\n            const canvas = document.createElement(\"canvas\");\n            const ctx = canvas.getContext(\"2d\");\n            if (!ctx) { reject(); return; }\n            canvas.width = imagedata.width;\n            canvas.height = imagedata.height;\n            ctx.putImageData(imagedata, 0, 0);\n            canvas.toBlob((blob) => {\n                if (!blob) { reject(); return; }\n                resolve(blob);\n            }, \"image\/jpeg\", 0.95);\n        } catch (error) {\n            reject(error);\n        }\n    });\n}\n\nconst arrRemove = (arr: Array&lt;any>, item: any) => {\n    const i = arr.indexOf(item);\n    if (i >= 0) { arr.splice(i, 1); }\n};\n\nconst filterUnique = () => {\n    const keys = new Set();\n    return (key: string) => !keys.has(key) ? keys.add(key) || true : false;\n};\n\n\nconst isSet = (x: any) => x != null; \/\/ works for [null, undefined], [ false, 0, Nan ] are true\nconst isNumber = (x: any) => typeof x === \"number\";\nconst isString = (x: any) => typeof x === \"string\";\nconst isIterable = (value: any) => Symbol.iterator in Object(value);\n\nfunction* iterator(iterable: Array&lt;any>) {\n    for (const item of iterable) { yield item; }\n}\nfunction* zipIterables(...iterables: Array&lt;any>) {\n    const iterators = iterables.map(x => iterator(x));\n\n    while (true) {\n        const stats = [];\n        const results = [];\n        for (const iterable of iterators) {\n            const result = iterable.next();\n            stats.push(result.done);\n            results.push(result.value);\n        }\n        if (stats.every((stat) => stat === true)) {\n            break;\n        }\n        yield results;\n    }\n}\n\nconst stringComparer = (locales?: any, options?: any) => {\n    if (!locales) { locales = \"en\"; }\n    if (!options) { options = { numeric: true, sensitivity: \"base\" }; }\n    const collator = new Intl.Collator(locales, options);\n    return (a: string, b: string) => collator.compare(a, b);\n};\nconst comparer = stringComparer();\nfunction compare(a: any, b: any, reverse: boolean): number {\n    const [x, y] = reverse ? [b, a] : [a, b];\n    if (!isSet(x) &amp;&amp; !isSet(y)) { return 0; }\n    if (!isSet(x) &amp;&amp; isSet(y)) { return -1; }\n    if (!isSet(y) &amp;&amp; isSet(x)) { return 1; }\n    if (isNumber(x) &amp;&amp; isNumber(y)) { return x - y; }\n    if (isString(x) &amp;&amp; isString(y)) { return comparer(x, y); }\n    if (isIterable(x) &amp;&amp; isIterable(y)) {\n        for (const d of Array.from(zipIterables(x, y))) {\n            const c = compare(d[0], d[1], reverse);\n            if (c !== 0) { return c; }\n        }\n        return 0;\n    }\n    return comparer(String(x), String(y));\n}\nexport function orderBy&lt;T>(array: T[], fn: (x: T) => any, reverse?: boolean) {\n    const result = Array.from(array);\n    result.sort((a, b) => compare(fn(a), fn(b), reverse || false));\n    return result;\n}\n\nexport function getDate(input: any): Date | undefined {\n    if (typeof input === \"object\" &amp;&amp; input instanceof Date) { return new Date(input); }\n    if (typeof input === \"string\") { return new Date(input); }\n    return undefined;\n}\n\nexport function numberFormat(value: number) {\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\");\n    return currencyFormatter.format(value);\n}\n\nexport function numberFormatRounded(value?: number, decimals: number = 0, unit: string = \"\") {\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\");\n    return currencyFormatter.format(round(value, decimals) ?? 0) + (unit ? ` ${unit}` : \"\");\n}\n\nexport function numberFormatRoundedWithoutSeperator(value?: number, decimals: number = 0, unit: string = \"\") {\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    \/\/ Use fixed-point notation to avoid thousand separators\n    const roundedValue = (value ?? 0).toFixed(decimals);\n    return roundedValue + (unit ? ` ${unit}` : \"\");\n}\n\nexport const normalizeKeysToLowercase = (obj: any) => {\n    return Object.keys(obj).reduce((acc, key) => {\n        const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1);\n        acc[lowercaseKey] = obj[key];\n        return acc;\n    }, {} as any);\n};\n\nexport function currencyFormat(value: number | undefined, currencyISO: string = \"EUR\"): string {\n    if (value === null || value === undefined) return \"\";\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\", { style: \"currency\", currency: currencyISO, minimumFractionDigits: 2, maximumFractionDigits: 6 });\n    return currencyFormatter.format(value);\n}\n\nexport function round(value: number | undefined, precision: number): number | undefined {\n    if (value === null || value === undefined) return undefined;\n    value = Number(value) || 0.0;\n    const multiplier = Math.pow(10, precision || 0);\n    return Math.round(value * multiplier) \/ multiplier;\n}\n\nexport function getNumberFromString(value: any): number | undefined {\n    if (value === null || value === undefined || value === \"\") return undefined;\n    const s = String(value).replace(\/[^\\d.,]\/, \"\");\n    const parts = s.split(\/[.,]\/);\n    if (parts.length > 1)\n        parts.splice(parts.length - 1, 0, \".\"); \/\/ insert at index\n    const j = parts.join(\"\");\n    return parseFloat(j) || 0.0;\n}\n\ninterface IGroup&lt;U> {\n    group: string;\n    data: U;\n}\nexport function groupBy&lt;T, U>(array: T[], fn: (item: T) => IGroup&lt;U>) {\n    const groups: { [key: string]: U[] } = {};\n    array.forEach((item) => {\n        const result = fn(item);\n        const group = JSON.stringify(result.group);\n        groups[group] = groups[group] || [];\n        groups[group].push(result.data);\n    });\n    return Object.keys(groups).map((group) => groups[group]);\n};\n\nexport const getDateXDaysAgo = (numOfDays: number, date = new Date()) => {\n    const daysAgo = new Date(date.getTime());\n    daysAgo.setDate(date.getDate() - numOfDays);\n    return daysAgo;\n}\n\nconst nameof = &lt;T>(name: Extract&lt;keyof T, string>): string => name;\n\nexport function propertyOf&lt;T>(name: keyof T) { return name; }\n\nexport function pushRange(arr: any[], itemsToPush: any[]) {\n    Array.prototype.push.apply(arr, itemsToPush);\n}\n\nexport interface IHaveParentId {\n    id: any;\n    parentId?: any;\n}\n\ninterface IHasChilds {\n    children: any[];\n}\n\nexport function createDataTree&lt;T extends IHasChilds>(dataset: IHaveParentId[]) {\n    const hashTable = new Map&lt;any, any>();\n    dataset.forEach(aData => hashTable.set(aData.id, { ...aData }));\n    const dataTree: T[] = [];\n    dataset.forEach(aData => {\n        if (aData.parentId) {\n            const parent = hashTable.get(aData.parentId);\n            parent.children ??= [];\n            parent.children.push(hashTable.get(aData.id));\n        }\n        else dataTree.push(hashTable.get(aData.id))\n    });\n\n    for (const v of hashTable.values()) {\n        if (v.children &amp;&amp; v.children.length &lt; 1)\n            delete v.children;\n    }\n    return dataTree;\n};\n\n\nconst toBase64 = (file: Blob) => new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = () => resolve(reader.result);\n    reader.onerror = error => reject(error);\n});\n\nfunction* rangeGen(from: number, to: number) {\n    let i = from;\n    while (i &lt;= to) {\n        yield i;\n        i++;\n    }\n}\n\nexport function range(from: number, to: number) {\n    return Array.from(rangeGen(from, to));\n}\n\nexport function sumArray&lt;T>(arr: T[], fn: (arg: T) => number): number {\n    return arr.reduce((p, c) => p + fn(c), 0);\n}\n\nexport function getCurrentWeek() {\n    return { year: getISOWeekYear(new Date()), week: getISOWeek(new Date()) };\n}\nexport function getWeeksBack(weekCount = 4) {\n    const now = new Date();\n    now.setDate(now.getDate() - 7 * weekCount);\n    return { year: getISOWeekYear(now), week: getISOWeek(now) };\n}\n\nexport function SetCurrentWeekStyle(yw: IYearWeek): React.CSSProperties {\n    const currentWeek = getCurrentWeek();\n    return getCurrentWeekStyle(weeksEqual(yw, currentWeek));\n}\n\nexport function getCurrentWeekStyle(isCurrentWeek: boolean): React.CSSProperties {\n    if (!isCurrentWeek) return {};\n    return {\n        fontWeight: \"bold\",\n        color: \"#5D3FD3\"\n    };\n}\n\nexport function isHttpSucces(status?: number) {\n    return !!status &amp;&amp; status >= 200 &amp;&amp; status &lt; 400;\n}\n\nexport function bytesToKBMB(bytes?: number) {\n    if (!bytes) return;\n    if (bytes &lt; 1024) {\n        return bytes + \" bytes\";\n    } else if (bytes &lt; 1024 * 1024) {\n        return (bytes \/ 1024).toFixed(2) + \" KB\";\n    } else {\n        return (bytes \/ (1024 * 1024)).toFixed(2) + \" MB\";\n    }\n}\n\nexport function GetYearWeekFromKey(yearWeekString: string) {\n    const parts = yearWeekString.split(\"-\");\n    const year = parseInt(parts[0], 10);\n    const week = parseInt(parts[1], 10);\n\n    const yearWeek = { key: yearWeekString, year: year, week: week } as IYearWeek\n    return yearWeek;\n}\n\nexport function truncateString(text: string, maxLength: number): string {\n    text = String(text);\n    if (text.length &lt;= maxLength) {\n        return text;\n    }\n    return text.slice(0, maxLength - 3) + \"...\";\n}\nexport function limitArraySize&lt;T>(array: T[], maxSize: number): T[] {\n    return array.slice(0, maxSize);\n}\n\n\nexport function IsNullOrEmpty(text: string) {\n    if (text === undefined || text === null || text === \"\")\n        return true;\n    return false;\n}\nexport const weeksEqual = (a: IYearWeek | undefined, b: IYearWeek | undefined) => getWeekKey(a) === getWeekKey(b);\nexport const getWeekKey = (yw: IYearWeek | undefined) => {\n    return `${yw?.year}-` + `${yw?.week}`.padStart(2, \"0\");\n}\nexport const getWeekKeyDefault = (yw: IYearWeek | undefined) => {\n    if (!yw) return undefined;\n    return `${yw?.year}-` + `${yw?.week}`.padStart(2, \"0\");\n}\n\nexport function isSubset(array1: string[], array2: string[]): boolean {\n    const set2 = new Set(array2);\n    return array1.every(element => set2.has(element));\n}\n\n\nexport const weekAsInt = (yw: IYearWeek) => (yw.year * 100) + yw.week;\n\nexport const joinClasses = (...args: any[]) => Array.from(args).filter(x => !!x).join(\" \");\n\nexport function clearTextSelection() {\n    const w = window as any;\n    const d = document as any;\n    if (w.getSelection) {\n        if (w.getSelection().empty) {  \/\/ Chrome\n            w.getSelection().empty();\n        } else if (w.getSelection().removeAllRanges) {  \/\/ Firefox\n            w.getSelection().removeAllRanges();\n        }\n    } else if (d.selection) {  \/\/ IE?\n        d.selection.empty();\n    }\n}\n\nexport function delay(ms: number) {\n    return new Promise((r) => setTimeout(r, ms));\n}\n\nexport function deserializeState&lt;T>(state: string | null, defValue: T): T {\n    if (!state) return defValue;\n    try {\n        return JSON.parse(atob(state));\n    } catch (error) {\n        return defValue;\n    }\n}\n\nexport function serializeState&lt;T>(state: T): string {\n    return btoa(JSON.stringify(state));\n}\n\n\n\/\/ Debounce function to limit the rate at which a function can fire.\nexport function debounce&lt;T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters&lt;T>) => void {\n    let timeout: ReturnType&lt;typeof setTimeout> | null;\n    return function (...args: Parameters&lt;T>) {\n        const later = () => {\n            if (timeout !== null) {\n                clearTimeout(timeout);\n            }\n            func(...args);\n        };\n        if (timeout !== null) {\n            clearTimeout(timeout);\n        }\n        timeout = setTimeout(later, wait);\n    };\n}\n\nexport function ensureArray(b: any): any[] {\n    if (Array.isArray(b))\n        return b;\n    if (!b) return [];\n    return [b];\n}\n\nexport function ensureTypedArray&lt;T>(b?: T[]): T[] {\n    if (Array.isArray(b))\n        return b;\n    if (!b) return [];\n    return [b];\n}\n\n\n\nexport { arrRemove, b64toBlob, createCounter, dateAdd, debounced, displayTextAreaProperly, eq, escapeHtml, filterUnique, getFirstItem, getLocalStorage, getNumber, getString, getToday, historyPager, htmlEncode, imagedataToBlob, isFileImage, isNumberKey, isset, isValidDate, lastWorkday, nameof, nextWorkDay, objToQueryString, onChange, propertiesEqual, remove, setLocalStorage, strEq, toBase64, unique };\n\n\n<\/pre><\/div>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">import moment from \"moment\";\n\nconst minimumValue = (value: number, min: number) => value &lt; min ? min : value;\n\nconst isLast = (arr: Array&lt;any>, i: number) => arr.length - 1 === i;\n\nconst nextItemInArray = (arr: Array&lt;any>, i: number) => arr[minimumValue(i + 1, arr.length - 1)];\n\nconst unique = (arr: Array&lt;any>) => Array.from(new Set([...arr]));\n\nexport function isValidNumber(x: any) {\n    return typeof (x) === \"number\" &amp;&amp; Number.isFinite(x);\n}\n\nexport const filterUnique = () => {\n    const keys = new Set();\n    return (key: string) => !keys.has(key) ? keys.add(key) || true : false;\n};\n\nconst getFileExtension = (str: string) => {\n    const splitted = str.split(\".\");\n    return splitted[splitted.length - 1].toLowerCase();\n};\n\nconst toBase64 = (file: Blob) => new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = () => resolve(reader.result);\n    reader.onerror = error => reject(error);\n});\n\nconst b64toBlob = (base64: string) => fetch(base64).then(res => res.blob());\n\nconst asyncForEach = async (array: Array&lt;any>, callback: Function) => {\n    for (let index = 0; index &lt; array.length; index++) {\n        await callback(array[index], index, array);\n    }\n};\n\nfunction escapeHtml(unsafe: any): string {\n    return String(unsafe)\n        .replace(\/&amp;\/g, \"&amp;\")\n        .replace(\/&lt;\/g, \"&lt;\")\n        .replace(\/>\/g, \">\")\n        .replace(\/\"\/g, \"\"\")\n        .replace(\/'\/g, \"'\");\n}\n\nconst any = (arr: Array&lt;any>) => arr?.length > 0;\n\nconst stringComparer = (locales?: any, options?: any) => {\n    if (!locales) locales = \"en\";\n    if (!options) options = { numeric: true, sensitivity: \"base\" };\n    const collator = new Intl.Collator(locales, options);\n    return (a: string, b: string) => collator.compare(a, b);\n};\n\nconst stringCompare = stringComparer();\n\nexport interface IFilterItem {\n    text: string;\n    value: any;\n}\n\nfunction getFilter&lt;T>(values: T[keyof T][], fn: (x: T[keyof T]) => IFilterItem) {\n    return Array.from(new Set(values)).map(fn);\n}\n\nfunction escapeRegExp(s: string) {\n    return String(s).replace(\/[.*+?^${}()|[\\]\\\\]\/g, \"\\\\$&amp;\"); \/\/ $&amp; means the whole matched string\n}\n\nfunction getPropertyValue&lt;T, K extends keyof T>(o: T, propertyName: K): T[K] {\n    return o[propertyName];\/\/ o[propertyName] is of type T[K]\n};\n\nconst createQuery = (data: any) => {\n    const c = encodeURIComponent;\n    const cv = (v: any) => v &amp;&amp; v.toISOString ? v.toISOString() : v;\n    return \"?\" + Object.keys(data).map(k => `${c(k)}=${c(cv(data[k]))}`).join(\"&amp;\");\n};\n\nconst stringIsNullOrEmpty = (str: any) => {\n    if (typeof str !== \"string\") return false;\n    if (str.trim().length &lt; 1) return false;\n    return true;\n};\n\nconst getCircularReplacer = () => {\n    const seen = new WeakSet();\n    return (key: any, value: any) => {\n        if (typeof value === \"object\" &amp;&amp; value !== null) {\n            if (seen.has(value)) {\n                return;\n            }\n            seen.add(value);\n        }\n        return value;\n    };\n};\n\nconst createGuid = (): string => {\n    let d = new Date().getTime();\n    const uuid = \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(\/[xy]\/g, function (c) {\n        const r = (d + Math.random() * 16) % 16 | 0;\n        d = Math.floor(d \/ 16);\n        \/\/ eslint-disable-next-line no-mixed-operators\n        return (c === \"x\" ? r : (r &amp; 0x3 | 0x8)).toString(16);\n    });\n    return uuid;\n};\n\nconst toJson = (x: any) => JSON.stringify(x, getCircularReplacer());\n\nconst parseJson = (x: any) => {\n    if (typeof x !== \"string\") return undefined;\n    try {\n        return JSON.parse(x);\n    } catch {\n        return undefined;\n    }\n};\n\nconst getLocalStorage = &lt;T>(key: string): T => parseJson(localStorage.getItem(key)) as T;\nconst setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state));\n\nconst getLocationStateKey = (path: string) => (\"state-\" + String(path)).toLowerCase();\nconst getLocationState = &lt;T>(path: string): T => parseJson(sessionStorage.getItem(getLocationStateKey(path))) as T;\nconst setLocationState = (path: string, state: any): any => sessionStorage.setItem(getLocationStateKey(path), toJson(state));\n\n\nexport const toSortableDate = (x?: Date) => `${x?.toISOString()}`;\nconst formatDate = (value: Date) => value ? moment(value).format(\"DD-MM-yyyy\") : \"\";\nconst formatDateTime = (value: Date) => value ? moment(value).format(\"DD-MM-yyyy HH:mm\") : \"\";\n\nexport function classes(...args: string[]) { return Array.from(args).filter(x => x).join(\" \"); }\n\nexport const limit = (v: number, min: number, max: number): number => Math.max(min, Math.min(max, Number(v) || 0));\n\ninterface IGroup&lt;U> {\n    group: string;\n    data: U;\n}\nexport function groupBy&lt;T, U>(array: T[], fn: (item: T) => IGroup&lt;U>) {\n    const groups: { [key: string]: U[] } = {};\n    array.forEach((item) => {\n        const result = fn(item);\n        const group = JSON.stringify(result.group);\n        groups[group] = groups[group] || [];\n        groups[group].push(result.data);\n    });\n    return Object.keys(groups).map((group) => groups[group]);\n};\n\nexport function round2Dec(num: number) {\n    return Math.round(num * 100) \/ 100;\n}\n\nfunction addPostfixIfNumber(input: string, postFix: string) {\n    if (!isNumeric(input)) {\n        return input;\n    }\n    return `${input}${postFix}`;\n}\n\nfunction isNumeric(str: any) {\n    return !isNaN(str) &amp;&amp; !isNaN(parseFloat(str));\n}\n\nexport { addPostfixIfNumber, any, asyncForEach, b64toBlob, createGuid, createQuery, escapeHtml, escapeRegExp, formatDate, formatDateTime, getFileExtension, getFilter, getLocalStorage, getLocationState, getPropertyValue, isLast, minimumValue, nextItemInArray, parseJson, setLocalStorage, setLocationState, stringCompare, stringComparer, stringIsNullOrEmpty, toBase64, toJson, unique };\n\/\/ --\n<\/pre><\/div>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"typescript\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">\/* eslint-disable @typescript-eslint\/no-explicit-any *\/\n\/\/ import { getISOWeek, getISOWeekYear } from \"date-fns\";\n\/\/ import { IYearWeek } from \"..\/domain\/general\/IYearWeek\";\n\n\nconst eq = (a: string, b: string) => a === b;\n\nconst strEq = (a?: string, b?: string) => String(a).trim().toLowerCase() === String(b).trim().toLowerCase();\n\nconst remove = (arr: any[], item: any) => {\n    const i = arr.indexOf(item);\n    if (i >= 0) { arr.splice(i, 1); }\n};\nconst isset = (x: any) => ![undefined, null].includes(x);\n\nconst getString = (x: any) => isset(x) ? `${x}` : \"\";\n\nconst getFirstItem = (obj: any) => {\n    if (isset(obj)) {\n        return Array.from(obj)[0];\n    }\n    return null;\n};\n\nexport const roundNumberUpBy = (n: number, roundBy: number) => Math.ceil(n * 1.0 \/ roundBy) * roundBy; \/\/ Only add up\nexport const roundNumberBy = (n: number, roundBy: number) => Math.round(n * 1.0 \/ roundBy) * roundBy;\nexport const roundNumber = (n: number) => roundNumberBy(n, 1);\n\nexport function isDigitsOnly(str: string): boolean {\n    return \/^\\d+$\/.test(str);\n}\n\nexport function getArray&lt;T>(data?: T[]) {\n    return Array.from(data || []);\n}\n\nexport type Dictionary&lt;T> = { [id: string]: T };\n\nconst htmlEncode = (source: string) => {\n    let i = 0;\n    let ch: string;\n    let peek = \"\";\n    const result: string[] = [];\n    let line: string[] = [];\n    \/\/ Stash the next character and advance the pointer\n    const next = () => {\n        peek = source.charAt(i);\n        i += 1;\n    };\n    \/\/ Start a new \"line\" of output, to be joined later by &lt;br \/>\n    const endline = () => {\n        result.push(line.join(\"\"));\n        line = [];\n    };\n    \/\/ Push a character or its entity onto the current line\n    const push = () => {\n        if (ch !== \"\\r\" &amp;&amp; ch !== \"\\n\" &amp;&amp; (ch &lt; \" \" || ch > \"~\")) {\n            line.push(`&amp;#${ch.charCodeAt(0)};`);\n        } else {\n            line.push(ch);\n        }\n    };\n    next();\n    while (i &lt;= source.length) {\n        ch = peek;\n        next();\n        switch (ch) {\n            case \"&lt;\":\n                line.push(\"&lt;\");\n                break;\n            case \">\":\n                line.push(\">\");\n                break;\n            case \"&amp;\":\n                line.push(\"&amp;\");\n                break;\n            case \"\\\"\":\n                line.push(\"\"\");\n                break;\n            case \"'\":\n                line.push(\"'\");\n                break;\n            default:\n                push();\n        }\n    }\n    endline();\n    return result.join(\"&lt;br \/>\");\n};\n\nconst unique = (arr: any[], valFn: (item: any) => any) => {\n    const values: { [key: string]: any } = {};\n    return arr.filter(item => {\n        const val = String(valFn(item));\n        return val in values ? false : values[val] = true;\n    });\n};\n\nconst propertiesEqual = (a: any, b: any, properties: string[]) => a &amp;&amp; b &amp;&amp; properties.every((p: any) => a[p] === b[p]);\n\nconst dateAdd = (date: Date, ms: number) => new Date(date.getTime() + ms);\nconst getToday = () => {\n    const d = new Date();\n    d.setHours(23, 59, 59);\n    return d;\n};\n\nexport interface IPageInfo { from?: Date; to?: Date; page: number; }\ntype Pager = (page: number) => IPageInfo;\n\nconst historyPager = (msRange: number): Pager => {\n    const today = getToday();\n    return (page: number) => {\n        const from = dateAdd(today, -msRange * (page + 1));\n        from.setHours(0, 0, 0);\n        let to: Date | undefined = dateAdd(today, -msRange * page);\n        if (page &lt; 1) {\n            to = undefined;\n        }\n        return {\n            from,\n            to,\n            page\n        };\n    };\n};\n\n\/\/ only skips sunday\nconst nextWorkDay = function (date: Date): Date {\n    const tomorrow = new Date(date.setDate(date.getDate() + 1));\n    return tomorrow.getDay() % 7 ? tomorrow : nextWorkDay(tomorrow);\n};\n\n\/\/ only skips sunday\nconst lastWorkday = function (date: Date): Date {\n    const yesterday = new Date(date.setDate(date.getDate() - 1));\n    return yesterday.getDay() % 7 ? yesterday : lastWorkday(yesterday);\n};\n\nconst isFileImage = (fileName: string) => {\n    const extensions = [\".JPG\", \".JPEG\", \".JPE\", \".BMP\", \".GIF\", \".PNG\"];\n    return extensions.some(x => fileName.toUpperCase().includes(x));\n};\n\nconst isValidDate = (d: any) => d instanceof Date &amp;&amp; !isNaN(d.getTime());\n\nfunction getNumber(x: any) {\n    const value = Number(String(x).replace(\/([^\\d])\/, \"\").replace(\/^0+\/, \"\"));\n    if (Number.isFinite(value)) return value;\n    return undefined;\n}\n\nconst isNumberKey = (event: any) => {\n    const charCode = (event.which) ? event.which : event.keyCode;\n    if (charCode !== 46 &amp;&amp; charCode > 31 &amp;&amp; (charCode &lt; 48 || charCode > 57)) {\n        return false;\n    }\n    return true;\n};\n\nconst escapeHtml = (unsafe: any): string => {\n    return String(unsafe)\n        .replace(\/&amp;\/g, \"&amp;\")\n        .replace(\/&lt;\/g, \"&lt;\")\n        .replace(\/>\/g, \">\")\n        .replace(\/\"\/g, \"\"\")\n        .replace(\/'\/g, \"'\");\n};\n\nconst onChange = (objToWatch: any, fn: any) => {\n    const handler = {\n        set(target: any, property: any, value: any) {\n            fn();\n            return Reflect.set(target, property, value);\n        },\n        deleteProperty(target: any, property: any) {\n            fn();\n            return Reflect.deleteProperty(target, property);\n        }\n    }; return new Proxy(objToWatch, handler);\n};\n\nfunction debounced(delay: number, fn: (...args: Array&lt;any>) => any) {\n    let timerId: any;\n    return function (...args: Array&lt;any>) {\n        if (timerId) {\n            clearTimeout(timerId);\n        }\n        timerId = setTimeout(() => {\n            fn(...args);\n            timerId = null;\n        }, delay);\n    };\n}\n\nconst displayTextAreaProperly = (str: string): string => {\n    if (str) {\n        return str.replace(\/\\n\/g, \"&lt;br>\");\n    }\n    return \"\";\n};\n\nconst createCounter = () => {\n    let c = 0;\n    return () => { c += 1; return c; };\n};\n\nconst getCircularReplacer = () => {\n    const seen = new WeakSet();\n    return (_key: any, value: any) => {\n        if (typeof value === \"object\" &amp;&amp; value !== null) {\n            if (seen.has(value)) {\n                return;\n            }\n            seen.add(value);\n        }\n        return value;\n    };\n};\n\nexport function getDateString(date: Date) {\n    const day = String(date.getDate()).padStart(2, '0');\n    const month = String(date.getMonth() + 1).padStart(2, '0');\n    const year = date.getFullYear();\n    return `${day}-${month}-${year}`;\n}\n\nexport function jsonCopy&lt;T>(x: T): T { return parseJson(toJson(x)) }\nconst toJson = (x: any) => JSON.stringify(x, getCircularReplacer());\nconst parseJson = (x: any) => {\n    if (typeof x !== \"string\") { return undefined; }\n    try {\n        return JSON.parse(x);\n    } catch {\n        return undefined;\n    }\n};\nexport const getLocalStorage = &lt;T>(key: string): T => parseJson(localStorage.getItem(key)) as T;\nexport const setLocalStorage = (key: string, state: any): any => localStorage.setItem(key, toJson(state));\nexport const removeLocalStorage = (key: string): any => localStorage.removeItem(key);\n\nexport const getSessionStorage = &lt;T>(key: string): T => parseJson(sessionStorage.getItem(key)) as T;\nexport const setSessionStorage = (key: string, state: any): any => sessionStorage.setItem(key, toJson(state));\n\nconst c = encodeURIComponent;\nconst cv = (v: any) => v &amp;&amp; v.toISOString ? v.toISOString() : v;\nconst objToQueryString = (obj: any) => Object.keys(obj).map(k => `${c(k)}=${c(cv(obj[k]))}`).join(\"&amp;\");\n\nfunction b64toBlob(b64Data: string, contentType: string, sliceSize?: number): Blob {\n    contentType = contentType || \"\";\n    sliceSize = sliceSize || 512;\n    const byteCharacters = atob(b64Data);\n    const byteArrays = [];\n\n    for (let offset = 0; offset &lt; byteCharacters.length; offset += sliceSize) {\n        const slice = byteCharacters.slice(offset, offset + sliceSize);\n        const byteNumbers = new Array(slice.length);\n        for (let i = 0; i &lt; slice.length; i++) {\n            byteNumbers[i] = slice.charCodeAt(i);\n        }\n        const byteArray = new Uint8Array(byteNumbers);\n        byteArrays.push(byteArray);\n    }\n    return new Blob(byteArrays, { type: contentType });\n}\n\nfunction imagedataToBlob(imagedata: ImageData): Promise&lt;Blob> {\n    return new Promise((resolve, reject) => {\n        try {\n            const canvas = document.createElement(\"canvas\");\n            const ctx = canvas.getContext(\"2d\");\n            if (!ctx) { reject(); return; }\n            canvas.width = imagedata.width;\n            canvas.height = imagedata.height;\n            ctx.putImageData(imagedata, 0, 0);\n            canvas.toBlob((blob) => {\n                if (!blob) { reject(); return; }\n                resolve(blob);\n            }, \"image\/jpeg\", 0.95);\n        } catch (error) {\n            reject(error);\n        }\n    });\n}\n\nconst arrRemove = (arr: Array&lt;any>, item: any) => {\n    const i = arr.indexOf(item);\n    if (i >= 0) { arr.splice(i, 1); }\n};\n\nconst filterUnique = () => {\n    const keys = new Set();\n    return (key: string) => !keys.has(key) ? keys.add(key) || true : false;\n};\n\n\nconst isSet = (x: any) => x != null; \/\/ works for [null, undefined], [ false, 0, Nan ] are true\nexport const isNumber = (x: any) => typeof x === \"number\" &amp;&amp; !Number.isNaN(x) &amp;&amp; Number.isFinite(x);\nconst isString = (x: any) => typeof x === \"string\";\nconst isIterable = (value: any) => Symbol.iterator in Object(value);\n\nfunction* iterator(iterable: Array&lt;any>) {\n    for (const item of iterable) { yield item; }\n}\nfunction* zipIterables(...iterables: Array&lt;any>) {\n    const iterators = iterables.map(x => iterator(x));\n\n    while (true) {\n        const stats = [];\n        const results = [];\n        for (const iterable of iterators) {\n            const result = iterable.next();\n            stats.push(result.done);\n            results.push(result.value);\n        }\n        if (stats.every((stat) => stat === true)) {\n            break;\n        }\n        yield results;\n    }\n}\n\nconst stringComparer = (locales?: any, options?: any) => {\n    if (!locales) { locales = \"en\"; }\n    if (!options) { options = { numeric: true, sensitivity: \"base\" }; }\n    const collator = new Intl.Collator(locales, options);\n    return (a: string, b: string) => collator.compare(a, b);\n};\nconst comparer = stringComparer();\nfunction compare(a: any, b: any, reverse: boolean): number {\n    const [x, y] = reverse ? [b, a] : [a, b];\n    if (!isSet(x) &amp;&amp; !isSet(y)) { return 0; }\n    if (!isSet(x) &amp;&amp; isSet(y)) { return -1; }\n    if (!isSet(y) &amp;&amp; isSet(x)) { return 1; }\n    if (isNumber(x) &amp;&amp; isNumber(y)) { return x - y; }\n    if (isString(x) &amp;&amp; isString(y)) { return comparer(x, y); }\n    if (isIterable(x) &amp;&amp; isIterable(y)) {\n        for (const d of Array.from(zipIterables(x, y))) {\n            const c = compare(d[0], d[1], reverse);\n            if (c !== 0) { return c; }\n        }\n        return 0;\n    }\n    return comparer(String(x), String(y));\n}\nexport function orderBy&lt;T>(array: T[], fn: (x: T) => any, reverse?: boolean) {\n    const result = Array.from(array);\n    result.sort((a, b) => compare(fn(a), fn(b), reverse || false));\n    return result;\n}\n\nexport function getDate(input: any): Date | undefined {\n    if (typeof input === \"object\" &amp;&amp; input instanceof Date) { return new Date(input); }\n    if (typeof input === \"string\") { return new Date(input); }\n    return undefined;\n}\n\nexport function numberFormat(value?: number, unit: string = \"\", showZero = false) {\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    if (!showZero &amp;&amp; value === 0) return \"\";\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\");\n    return currencyFormatter.format(value) + (unit ? ` ${unit}` : \"\");\n}\n\nexport function numberFormatRounded(value?: number, decimals: number = 0, unit: string = \"\", showZero = false) {\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    if (!showZero &amp;&amp; value === 0) return \"\";\n    const roundedValue = round(value || 0, decimals) ?? 0;\n    const isInteger = roundedValue % 1 === 0;\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\", {\n        minimumFractionDigits: isInteger ? 0 : decimals,\n        maximumFractionDigits: decimals\n    });\n    return currencyFormatter.format(roundedValue) + (unit ? ` ${unit}` : \"\");\n}\n\n\n\nexport function currencyFormat(value: number | undefined, currencyISO: string = \"EUR\"): string {\n    if (value === null || value === undefined) return \"\";\n    if (typeof (value) !== \"number\" || Number.isNaN(value) || !Number.isFinite(value)) return \"\";\n    const currencyFormatter = new Intl.NumberFormat(\"nl-NL\", { style: \"currency\", currency: currencyISO, minimumFractionDigits: 2, maximumFractionDigits: 6 });\n    return currencyFormatter.format(value);\n}\n\nexport function round(value: number | undefined, precision: number): number | undefined {\n    if (value === null || value === undefined) return undefined;\n    value = Number(value) || 0.0;\n    const multiplier = Math.pow(10, precision || 0);\n    return Math.round(value * multiplier) \/ multiplier;\n}\n\nexport function getNumberFromString(value: any): number | undefined {\n    if (value === null || value === undefined || value === \"\") return undefined;\n    const s = String(value).replace(\/[^\\d.,]\/, \"\");\n    const parts = s.split(\/[.,]\/);\n    if (parts.length > 1)\n        parts.splice(parts.length - 1, 0, \".\"); \/\/ insert at index\n    const j = parts.join(\"\");\n    return parseFloat(j) || 0.0;\n}\n\nexport const doubleIsEqualTo = (a: number, b: number, maxDiff: number = Number.EPSILON) => Math.abs(b - a) &lt;= maxDiff;\ninterface IGroup&lt;U> {\n    group: string;\n    data: U;\n}\nexport function groupBy&lt;T, U>(array: T[], fn: (item: T) => IGroup&lt;U>) {\n    const groups: { [key: string]: U[] } = {};\n    array.forEach((item) => {\n        const result = fn(item);\n        const group = JSON.stringify(result.group);\n        groups[group] = groups[group] || [];\n        groups[group].push(result.data);\n    });\n    return Object.keys(groups).map((group) => groups[group]);\n}\n\nexport const getDateXDaysAgo = (numOfDays: number, date = new Date()) => {\n    const daysAgo = new Date(date.getTime());\n    daysAgo.setDate(date.getDate() - numOfDays);\n    return daysAgo;\n}\n\nconst nameof = &lt;T>(name: Extract&lt;keyof T, string>): string => name;\n\nexport function propertyOf&lt;T>(name: keyof T) { return name; }\n\nexport function pushRange(arr: any[], itemsToPush: any[]) {\n    Array.prototype.push.apply(arr, itemsToPush);\n}\n\nexport interface IHaveParentId {\n    id: any;\n    parentId?: any;\n}\n\ninterface IHasChilds {\n    children: any[];\n}\n\nexport function createDataTree&lt;T extends IHasChilds>(dataset: IHaveParentId[]) {\n    const hashTable = Object.create(null);\n    dataset.forEach(aData => hashTable[aData.id] = { ...aData });\n    const dataTree: T[] = [];\n    dataset.forEach(aData => {\n        if (aData.parentId) {\n            const parent = hashTable[aData.parentId];\n            parent.children ??= [];\n            parent.children.push(hashTable[aData.id]);\n        }\n        else dataTree.push(hashTable[aData.id])\n    });\n    return dataTree;\n}\n\n\nconst toBase64 = (file: Blob) => new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.readAsDataURL(file);\n    reader.onload = () => resolve(reader.result);\n    reader.onerror = error => reject(error);\n});\n\nfunction* rangeGen(from: number, to: number) {\n    let i = from;\n    while (i &lt;= to) {\n        yield i;\n        i++;\n    }\n}\n\nexport function range(from: number, to: number) {\n    return Array.from(rangeGen(from, to));\n}\n\nexport function sumArray&lt;T>(arr: T[], fn: (arg: T) => number): number {\n    return arr.reduce((p, c) => p + fn(c), 0);\n}\n\nexport function isHttpSucces(status?: number) {\n    return !!status &amp;&amp; status >= 200 &amp;&amp; status &lt; 400;\n}\n\nexport function bytesToKBMB(bytes?: number) {\n    if (!bytes) return;\n    if (bytes &lt; 1024) {\n        return bytes + \" bytes\";\n    } else if (bytes &lt; 1024 * 1024) {\n        return (bytes \/ 1024).toFixed(2) + \" KB\";\n    } else {\n        return (bytes \/ (1024 * 1024)).toFixed(2) + \" MB\";\n    }\n}\n\nexport function IsNullOrEmpty(text: string) {\n    if (text === undefined || text === null || text === \"\")\n        return true;\n    return false;\n}\n\nexport async function delay(ms: number) {\n    return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport interface IPathPart {\n    name: string;\n    path: string;\n    isRootPath: boolean;\n}\n\nexport function splitStringOnBackslashWithPath(str: string, rootPath: string): IPathPart[] {\n    const splitted = str.split(\"\\\\\");\n    const response: IPathPart[] = [];\n    const prevPath: string[] = [];\n    for (const item of splitted) {\n        prevPath.push(item);\n        const path = prevPath.join(\"\\\\\");\n        response.push({\n            name: item,\n            path: path,\n            isRootPath: rootPath.includes(path)\n        })\n    }\n    return response;\n}\n\nexport function getDateFromNumber(numberDate: number): string {\n    const dateStr = numberDate.toString();\n\n    const year = parseInt(dateStr.substring(0, 4), 10);\n    const month = parseInt(dateStr.substring(4, 6), 10);\n    const day = parseInt(dateStr.substring(6, 8), 10);\n\n    const date = new Date(year, month - 1, day); \/\/ month is 0-indexed in Date\n\n    const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', year: 'numeric' };\n    return new Intl.DateTimeFormat('en-GB', options).format(date);\n}\n\nexport const classes = (...args: any[]) => Array.from(args).map(x => String(x).trim()).filter(x => !!x).join(\" \");\n\nexport { arrRemove, b64toBlob, createCounter, dateAdd, debounced, displayTextAreaProperly, eq, escapeHtml, filterUnique, getFirstItem, getNumber, getString, getToday, historyPager, htmlEncode, imagedataToBlob, isFileImage, isNumberKey, isset, isValidDate, lastWorkday, nameof, nextWorkDay, objToQueryString, onChange, propertiesEqual, remove, strEq, toBase64, unique };\n\n<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Iterables \/ Arrays<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9392","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9392","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=9392"}],"version-history":[{"count":10,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9392\/revisions"}],"predecessor-version":[{"id":9522,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/9392\/revisions\/9522"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=9392"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=9392"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=9392"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}