import { integerToAmount, amountToInteger, currencyToAmount } from './util'; // For now, this info is duplicated from the backend. Figure out how // to share it later. export const TYPE_INFO = { date: { ops: ['is', 'isapprox', 'gt', 'gte', 'lt', 'lte'], nullable: false }, id: { ops: ['is', 'contains', 'oneOf'], nullable: true }, string: { ops: ['is', 'contains', 'oneOf'], nullable: false }, number: { ops: ['is', 'isapprox', 'isbetween', 'gt', 'gte', 'lt', 'lte'], nullable: false }, boolean: { ops: ['is'], nullable: false } }; export const FIELD_TYPES = new Map( Object.entries({ imported_payee: 'string', payee: 'id', date: 'date', notes: 'string', amount: 'number', amountInflow: 'number', amountOutfow: 'number', category: 'id', account: 'id', cleared: 'boolean' }) ); export function mapField(field, opts) { opts = opts || {}; switch (field) { case 'imported_payee': return 'imported payee'; case 'amount': if (opts.inflow) { return 'amount (inflow)'; } else if (opts.outflow) { return 'amount (outflow)'; } return 'amount'; case 'amount-inflow': return 'amount (inflow)'; case 'amount-outflow': return 'amount (outflow)'; case 'cleared': return 'cleared'; default: return field; } } export function friendlyOp(op, type) { switch (op) { case 'oneOf': return 'one of'; case 'is': return 'is'; case 'isapprox': return 'is approx'; case 'isbetween': return 'is between'; case 'contains': return 'contains'; case 'gt': if (type === 'date') { return 'is after'; } return 'is greater than'; case 'gte': if (type === 'date') { return 'is after or equals'; } return 'is greater than or equals'; case 'lt': if (type === 'date') { return 'is before'; } return 'is less than'; case 'lte': if (type === 'date') { return 'is before or equals'; } return 'is less than or equals'; case 'true': return 'is true'; case 'false': return 'is false'; case 'set': return 'set'; case 'link-schedule': return 'link schedule'; default: return ''; } } export function deserializeField(field) { if (field === 'amount-inflow') { return { field: 'amount', options: { inflow: true } }; } else if (field === 'amount-outflow') { return { field: 'amount', options: { outflow: true } }; } else { return { field }; } } export function getFieldError(type) { switch (type) { case 'date-format': return 'Invalid date format'; case 'no-null': case 'no-empty-array': case 'no-empty-string': return 'Value cannot be empty'; case 'not-number': return 'Value must be a number'; case 'invalid-field': return 'Please choose a valid field for this type of rule'; default: return 'Internal error, sorry! Contact help@actualbudget.com'; } } export function sortNumbers(num1, num2) { if (num1 < num2) { return [num1, num2]; } return [num2, num1]; } export function parse(item) { switch (item.type) { case 'number': { let parsed = item.value; if (item.field === 'amount' && item.op !== 'isbetween') { parsed = integerToAmount(parsed); } return { ...item, value: parsed }; } case 'string': { let parsed = item.value == null ? '' : item.value; return { ...item, value: parsed }; } case 'boolean': { let parsed = item.value; return { ...item, value: parsed }; } default: } return { ...item, error: null }; } export function unparse({ error, inputKey, ...item }) { switch (item.type) { case 'number': { let unparsed = item.value; if (item.field === 'amount' && item.op !== 'isbetween') { unparsed = amountToInteger(unparsed); } return { ...item, value: unparsed }; } case 'string': { let unparsed = item.value == null ? '' : item.value; return { ...item, value: unparsed }; } case 'boolean': { let unparsed = item.value == null ? false : item.value; return { ...item, value: unparsed }; } default: } return item; } export function makeValue(value, cond) { switch (cond.type) { case 'number': { if (cond.op !== 'isbetween') { return { ...cond, error: null, value: value ? currencyToAmount(value) || 0 : 0 }; } break; } default: } return { ...cond, error: null, value }; } export function getApproxNumberThreshold(number) { return Math.round(Math.abs(number) * 0.075); }