import React from 'react'; import moment from 'moment'; import _ from 'lodash'; import * as R from 'ramda'; import Currencies from 'js-money/lib/currency'; import { Intent } from '@blueprintjs/core'; import Currency from 'js-money/lib/currency'; import accounting from 'accounting'; import deepMapKeys from 'deep-map-keys'; import { createSelectorCreator, defaultMemoize } from 'reselect'; import { isEqual } from 'lodash'; import jsCookie from 'js-cookie'; export const getCookie = (name, defaultValue) => _.defaultTo(jsCookie.get(name), defaultValue); export const setCookie = (name, value, expiry = 365, secure = false) => { jsCookie.set(name, value, { expires: expiry, path: '/', secure }); }; export const removeCookie = (name) => { return jsCookie.remove(name, { path: '/' }); } export function removeEmptyFromObject(obj) { obj = Object.assign({}, obj); var keys = Object.keys(obj); keys.forEach(function (key) { const value = obj[key]; if (value === '' || value === null || value === undefined) { delete obj[key]; } }); return obj; } export const optionsMapToArray = (optionsMap, service = '') => { return Object.keys(optionsMap).map((optionKey) => { const optionValue = optionsMap[optionKey]; return { key: service ? `${service}_${optionKey}` : `${optionKey}`, value: optionValue, }; }); }; export const optionsArrayToMap = (optionsArray) => { return optionsArray.reduce((map, option) => { map[option.key] = option.value; return map; }, {}); }; export function numberComma(number) { number = typeof number === 'number' ? String(number) : number; const parts = number.split('.'); const integer = parts[0] || '0'; const decimal = parts[1]; const postfix = decimal ? `.${decimal}` : ''; return `${integer.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')}${postfix}`; } export const momentFormatter = (format) => { return { formatDate: (date) => moment(date).format(format), parseDate: (str) => moment(str, format).toDate(), placeholder: `${format}`, }; }; /** Event handler that exposes the target element's value as a boolean. */ export const handleBooleanChange = (handler) => { return (event) => handler(event.target.checked); }; /** Event handler that exposes the target element's value as a string. */ export const handleStringChange = (handler) => { return (event) => handler(event.target.value); }; /** Event handler that exposes the target element's value as a number. */ export const handleNumberChange = (handler) => { return handleStringChange((value) => handler(+value)); }; export const handleDateChange = (handler) => { return (date) => handler(moment(date).format('YYYY-MM-DD'), date); }; export const objectKeysTransform = (obj, transform) => { return Object.keys(obj).reduce((acc, key) => { const computedKey = transform(key); acc[computedKey] = obj[key]; return acc; }, {}); }; export const compose = (...funcs) => funcs.reduce( (a, b) => (...args) => a(b(...args)), (arg) => arg, ); export const getObjectDiff = (a, b) => { return _.reduce( a, (result, value, key) => { return _.isEqual(value, b[key]) ? result : result.concat(key); }, [], ); }; export const parseDateRangeQuery = (keyword) => { const queries = { today: { range: 'day', }, this_year: { range: 'year', }, this_month: { range: 'month', }, this_week: { range: 'week', }, }; if (typeof queries[keyword] === 'undefined') { throw new Error(`The date range query ${keyword} is not defined.`); } const query = queries[keyword]; return { fromDate: moment().startOf(query.range).toDate(), toDate: moment().endOf(query.range).toDate(), }; }; export const defaultExpanderReducer = (tableRows, level) => { const expended = []; const walker = (rows, parentIndex = null, currentLevel = 1) => { return rows.forEach((row, index) => { const _index = parentIndex ? `${parentIndex}.${index}` : `${index}`; expended[_index] = true; if (row.children && currentLevel < level) { walker(row.children, _index, currentLevel + 1); } }, {}); }; walker(tableRows); return expended; }; export function formattedAmount(cents, currency, props) { const { symbol, decimal_digits: precision } = Currency[currency]; const parsedProps = { noZero: false, ...props, }; const formatOptions = { symbol, precision: 0, format: { pos: '%s%v', neg: '%s-%v', zero: parsedProps.noZero ? '' : '%s%v', }, }; return accounting.formatMoney(cents, formatOptions); } export function formattedExchangeRate(amount, currency) { const options = { style: 'currency', currency: currency, minimumFractionDigits: 2, }; const formatter = new Intl.NumberFormat(undefined, options); return formatter.format(amount); } export const ConditionalWrapper = ({ condition, wrapper, children, ...rest }) => condition ? wrapper({ children, ...rest }) : children; export const checkRequiredProperties = (obj, properties) => { return properties.some((prop) => { const value = obj[prop]; return value === '' || value === null || value === undefined; }); }; export const saveFilesInAsync = (files, actionCb, extraTasks) => { const opers = []; files.forEach((file) => { const formData = new FormData(); formData.append('attachment', file.file); // const oper = new PProgress((resolve, reject, progress) => { // actionCb(formData, file, (requestProgress) => { // progress(requestProgress); // }) // .then((data) => { // resolve(data); // }) // .catch((error) => { // reject(error); // }); // }); // opers.push(oper); }); // return PProgress.all(opers); }; export const firstLettersArgs = (...args) => { let letters = []; args.forEach((word) => { if (typeof word === 'string') { letters.push(word.charAt(0)); } }); return letters.join('').toUpperCase(); }; export const uniqueMultiProps = (items, props) => { return _.uniqBy(items, (item) => { return JSON.stringify(_.pick(item, props)); }); }; export const transformUpdatedRows = (rows, rowIndex, columnIdOrObj, value) => { const columnId = typeof columnIdOrObj !== 'object' ? columnIdOrObj : null; const updateTable = typeof columnIdOrObj === 'object' ? columnIdOrObj : null; const newData = updateTable ? updateTable : { [columnId]: value }; return rows.map((row, index) => { if (index === rowIndex) { return { ...rows[rowIndex], ...newData }; } return { ...row }; }); }; export const tansformDateValue = (date) => { return moment(date).toDate() || new Date(); }; export const repeatValue = (value, len) => { var arr = []; for (var i = 0; i < len; i++) { arr.push(value); } return arr; }; export const flatToNestedArray = ( data, config = { id: 'id', parentId: 'parent_id' }, ) => { const map = {}; const nestedArray = []; data.forEach((item) => { map[item[config.id]] = item; map[item[config.id]].children = []; }); data.forEach((item) => { const parentItemId = item[config.parentId]; if (!item[config.parentId]) { nestedArray.push(item); } if (parentItemId && map[parentItemId]) { map[parentItemId].children.push(item); } }); return nestedArray; }; export const orderingLinesIndexes = (lines, attribute = 'index') => { return lines.map((line, index) => ({ ...line, [attribute]: index + 1 })); }; export const transformToObject = (arr, key) => { return arr.reduce(function (acc, cur, i) { acc[key ? cur[key] : i] = cur; return acc; }, {}); }; export const itemsStartWith = (items, char) => { return items.filter((item) => item.indexOf(char) === 0); }; export const saveInvoke = (func, ...rest) => { return func && func(...rest); }; export const safeInvoke = (func, ...rest) => { return func && func(...rest); }; export const transformToForm = (obj, emptyInitialValues) => { return _.pickBy( obj, (val, key) => val !== null && Object.keys(emptyInitialValues).includes(key), ); }; export function inputIntent({ error, touched }) { return error && touched ? Intent.DANGER : ''; } export function listToTree( inputList, { idFieldKey = 'id', parentFieldKey = 'parent_account_id', nodeMapper = (node) => ({ ...node }), }, ) { var map = {}, node, roots = [], i; const list = inputList.map((item) => nodeMapper(item)); for (i = 0; i < list.length; i += 1) { map[list[i][idFieldKey]] = i; list[i].children = []; } for (i = 0; i < list.length; i += 1) { node = list[i]; if (node[parentFieldKey]) { list[map[node[parentFieldKey]]].children.push(node); } else { roots.push(node); } } return roots; } export function treeToList( list, { idFieldKey = 'id', childrenFieldKey = 'children', nodeMapper = (node, depth) => node, nodeFilter = (node, depth) => true, }, ) { let depth = 0; const walker = (tree) => { return tree.reduce((acc, o) => { depth += 1; if (o[idFieldKey] && nodeFilter(o, depth)) { acc.push(nodeMapper(o, depth)); } if (o[childrenFieldKey]) { acc = acc.concat(walker(o.children)); } depth -= 1; return acc; }, []); }; return walker(list); } export function defaultToTransform( value, defaultOrTransformedValue, defaultValue, ) { const _defaultValue = typeof defaultValue === 'undefined' ? defaultOrTransformedValue : defaultValue; const _transfromedValue = typeof defaultValue === 'undefined' ? value : defaultOrTransformedValue; return value == null || value !== value || value === '' ? _defaultValue : _transfromedValue; } export function isBlank(value) { return (_.isEmpty(value) && !_.isNumber(value)) || _.isNaN(value); } export const getColumnWidth = ( rows, accessor, { maxWidth, minWidth, magicSpacing = 14 }, ) => { const cellLength = Math.max( ...rows.map((row) => (`${_.get(row, accessor)}` || '').length), ); let result = cellLength * magicSpacing; result = minWidth ? Math.max(minWidth, result) : result; result = maxWidth ? Math.min(maxWidth, result) : result; return result; }; export const getForceWidth = (text, magicSpacing = 14) => { const textLength = text.length; const result = textLength * magicSpacing; return result; }; export const toSafeNumber = (number) => { return _.toNumber(_.defaultTo(number, 0)); }; export const transformToCamelCase = (object) => { return deepMapKeys(object, (key) => _.camelCase(key)); }; export const transfromToSnakeCase = (object) => { return deepMapKeys(object, (key) => _.snakeCase(key)); }; export const transformTableQueryToParams = (object) => { return transfromToSnakeCase(object); }; export function flatObject(obj) { const flatObject = {}; const path = []; // current path function dig(obj) { if (obj !== Object(obj)) { return (flatObject[path.join('.')] = obj); } for (let key in obj) { path.push(key); dig(obj[key]); path.pop(); } } dig(obj); return flatObject; } export function randomNumber(min, max) { if (min > max) { let temp = max; max = min; min = temp; } if (min <= 0) { return Math.floor(Math.random() * (max + Math.abs(min) + 1)) + min; } else { return Math.floor(Math.random() * (max - min + 1)) + min; } } export function transformResponse(response) { return transformToCamelCase(response); } export function transactionNumber(prefix, number) { const codes = []; if (prefix) { codes.push(prefix); } if (number) { codes.push(number); } return codes.join(''); } export function safeCallback(callback, ...args) { return () => callback && callback(...args); } export const createDeepEqualSelector = createSelectorCreator( defaultMemoize, isEqual, ); /** * Detarmines whether the table has empty status. */ export const isTableEmptyStatus = ({ data, pagination, filterMeta }) => { return [ _.isEmpty(data), _.isEmpty(filterMeta.view), pagination.page === 1, ].every((cond) => cond === true); }; /** * Transformes the pagination meta to table props. */ export function getPagesCountFromPaginationMeta(pagination) { const { pageSize, total } = pagination; return Math.ceil(total / pageSize); } function transformFilterRoles(filterRoles) { return JSON.stringify(filterRoles); } /** * Transformes the table state to url query. */ export function transformTableStateToQuery(tableState) { const { pageSize, pageIndex, viewSlug, sortBy } = tableState; const query = { pageSize, page: pageIndex + 1, ...(tableState.filterRoles ? { stringified_filter_roles: transformFilterRoles( tableState.filterRoles, ), } : {}), ...(viewSlug ? { viewSlug } : {}), ...(Array.isArray(sortBy) && sortBy.length > 0 ? { column_sort_by: sortBy[0].id, sort_order: sortBy[0].desc ? 'desc' : 'asc', } : {}), }; return transfromToSnakeCase(query); } /** * Transformes the global table state to table state. */ export function globalTableStateToTable(globalState) { return { ..._.omit(globalState, ['customViewId']), }; } /** * Transformes the pagination meta repsonse. */ export function transformPagination(pagination) { const transformed = transformResponse(pagination); return { ...transformed, pagesCount: getPagesCountFromPaginationMeta(transformed), }; } export function removeRowsByIndex(rows, rowIndex) { const removeIndex = parseInt(rowIndex, 10); const newRows = rows.filter((row, index) => index !== removeIndex); return newRows; } export function safeSumBy(entries, getter) { return _.chain(entries) .map((row) => toSafeNumber(_.get(row, getter))) .sum() .value(); } export const fullAmountPaymentEntries = (entries) => { return entries.map((item) => ({ ...item, payment_amount: item.due_amount, })); }; export const amountPaymentEntries = (amount, entries) => { let total = amount; return entries.map((item) => { const diff = Math.min(item.due_amount, total); total -= Math.max(diff, 0); return { ...item, payment_amount: diff, }; }); }; export const updateAutoAddNewLine = (defaultEntry, props) => (entries) => { const newEntries = [...entries]; const lastEntry = _.last(newEntries); const newLine = props.filter((entryKey) => !isBlank(lastEntry[entryKey])); return newLine.length > 0 ? [...entries, defaultEntry] : [...entries]; }; /** * Ensure min entries lines. * @param {number} min * @param {any} defaultEntry */ export const updateMinEntriesLines = (min, defaultEntry) => (lines) => { if (lines.length < min) { const diffLines = Math.max(min - lines.length, 0); return [...lines, ...repeatValue(defaultEntry, diffLines)]; } return lines; }; export const updateRemoveLineByIndex = (rowIndex) => (entries) => { const removeIndex = parseInt(rowIndex, 10); return entries.filter((row, index) => index !== removeIndex); }; export const updateTableRow = (rowIndex, columnId, value) => (old) => { return old.map((row, index) => { if (index === rowIndex) { return { ...old[rowIndex], [columnId]: value, }; } return row; }); }; export const transformGeneralSettings = (data) => { return _.mapKeys(data, (value, key) => _.snakeCase(key)); }; export const calculateStatus = (paymentAmount, balanceAmount) => { return _.round(paymentAmount / balanceAmount, 2); }; const getCurrenciesOptions = () => { return Object.keys(Currencies).map((currencyCode) => { const currency = Currencies[currencyCode]; return { ...currency, currency_code: currencyCode, formatted_name: `${currencyCode} - ${currency.name}`, }; }); }; export const currenciesOptions = getCurrenciesOptions(); /** * Deeply get a value from an object via its path. */ function getIn(obj, key, def, p = 0) { const path = _.toPath(key); while (obj && p < path.length) { obj = obj[path[p++]]; } return obj === undefined ? def : obj; } export const defaultFastFieldShouldUpdate = (props, prevProps) => { return ( props.name !== prevProps.name || getIn(props.formik.values, prevProps.name) !== getIn(prevProps.formik.values, prevProps.name) || getIn(props.formik.errors, prevProps.name) !== getIn(prevProps.formik.errors, prevProps.name) || getIn(props.formik.touched, prevProps.name) !== getIn(prevProps.formik.touched, prevProps.name) || Object.keys(prevProps).length !== Object.keys(props).length || props.formik.isSubmitting !== prevProps.formik.isSubmitting ); }; export const ensureEntriesHasEmptyLine = R.curry( (minLinesNumber, defaultEntry, entries) => { if (entries.length >= minLinesNumber) { return [...entries, defaultEntry]; } return entries; }, ); export const transfromViewsToTabs = (views) => { return views.map((view) => ({ ..._.pick(view, ['slug', 'name']) })); }; export function nestedArrayToflatten( collection, property = 'children', parseItem = (a, level) => a, level = 1, ) { const parseObject = (obj) => parseItem( { ..._.omit(obj, [property]), level, }, level, ); return collection.reduce((items, currentValue, index) => { let localItems = [...items]; const parsedItem = parseObject(currentValue, level); localItems.push(parsedItem); if (Array.isArray(currentValue[property])) { const flattenArray = nestedArrayToflatten( currentValue[property], property, parseItem, level + 1, ); localItems = _.concat(localItems, flattenArray); } return localItems; }, []); } export function getFieldsFromResourceMeta(resourceFields) { const fields = Object.keys(resourceFields) .map((fieldKey) => { const field = resourceFields[fieldKey]; return { ...transformToCamelCase(field), key: fieldKey, }; }) .filter((field) => field.filterable !== false); return _.orderBy(fields, ['label']); } export function getFilterableFieldsFromFields(fields) { return fields.filter((field) => field.filterable !== false); } export const RESORUCE_TYPE = { ACCOUNTS: 'account', ITEMS: 'items', } function escapeRegExpChars(text) { return text.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } export function highlightText(text, query) { let lastIndex = 0; const words = query .split(/\s+/) .filter((word) => word.length > 0) .map(escapeRegExpChars); if (words.length === 0) { return [text]; } const regexp = new RegExp(words.join('|'), 'gi'); const tokens = []; while (true) { const match = regexp.exec(text); if (!match) { break; } const length = match[0].length; const before = text.slice(lastIndex, regexp.lastIndex - length); if (before.length > 0) { tokens.push(before); } lastIndex = regexp.lastIndex; tokens.push({match[0]}); } const rest = text.slice(lastIndex); if (rest.length > 0) { tokens.push(rest); } return tokens; }