import React, { useState, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { createPayee } from 'loot-core/src/client/actions/queries';
import { useCachedAccounts } from 'loot-core/src/client/data-hooks/accounts';
import { useCachedPayees } from 'loot-core/src/client/data-hooks/payees';
import { getActivePayees } from 'loot-core/src/client/reducers/queries';
import { colors } from '../style';
import Add from '../svg/v1/Add';
import Autocomplete, {
defaultFilterSuggestion,
AutocompleteFooter,
AutocompleteFooterButton
} from './Autocomplete';
import { View } from './common';
function getPayeeSuggestions(payees, focusTransferPayees, accounts) {
let activePayees = accounts ? getActivePayees(payees, accounts) : payees;
if (focusTransferPayees && activePayees) {
activePayees = activePayees.filter(p => !!p.transfer_acct);
}
return activePayees || [];
}
function makeNew(value, rawPayee) {
if (value === 'new' && !rawPayee.current.startsWith('new:')) {
return 'new:' + rawPayee.current;
}
return value;
}
// Convert the fully resolved new value into the 'new' id that can be
// looked up in the suggestions
function stripNew(value) {
if (typeof value === 'string' && value.startsWith('new:')) {
return 'new';
}
return value;
}
export function PayeeList({
items,
getItemProps,
highlightedIndex,
embedded,
inputValue,
footer
}) {
let isFiltered = items.filtered;
let createNew = null;
items = [...items];
// If the "new payee" item exists, create it as a special-cased item
// with the value of the input so it always shows whatever the user
// entered
if (items[0].id === 'new') {
let [first, ...rest] = items;
createNew = first;
items = rest;
}
let offset = createNew ? 1 : 0;
let lastType = null;
return (
{createNew && (
Create Payee "{inputValue}"
)}
{items.map((item, idx) => {
let type = item.transfer_acct ? 'account' : 'payee';
let title;
if (type === 'payee' && lastType !== type) {
title = 'Payees';
} else if (type === 'account' && lastType !== type) {
title = 'Transfer To/From';
}
let showMoreMessage = idx === items.length - 1 && isFiltered;
lastType = type;
return (
{title && (
{title}
)}
{item.name}
{showMoreMessage && (
More payees are available, search to find them
)}
);
})}
{footer}
);
}
export default function PayeeAutocomplete({
value,
inputProps,
showMakeTransfer = true,
showManagePayees = false,
defaultFocusTransferPayees = false,
tableBehavior,
embedded,
onUpdate,
onSelect,
onManagePayees,
...props
}) {
let payees = useCachedPayees();
let accounts = useCachedAccounts();
let [focusTransferPayees, setFocusTransferPayees] = useState(
defaultFocusTransferPayees
);
let payeeSuggestions = useMemo(
() => [
{ id: 'new', name: '' },
...getPayeeSuggestions(payees, focusTransferPayees, accounts)
],
[payees, focusTransferPayees, accounts]
);
let rawPayee = useRef('');
let dispatch = useDispatch();
async function handleSelect(value) {
if (tableBehavior) {
onSelect && onSelect(makeNew(value, rawPayee));
} else {
let create = () => dispatch(createPayee(rawPayee.current));
if (Array.isArray(value)) {
value = await Promise.all(value.map(v => (v === 'new' ? create() : v)));
} else {
if (value === 'new') {
value = await create();
}
}
onSelect && onSelect(value);
}
}
return (
{
if (!item) {
return '';
} else if (item.id === 'new') {
return rawPayee.current;
}
return item.name;
}}
inputProps={{
...inputProps,
onChange: text => (rawPayee.current = text)
}}
onUpdate={value => onUpdate && onUpdate(makeNew(value, rawPayee))}
onSelect={handleSelect}
getHighlightedIndex={suggestions => {
if (suggestions.length > 1 && suggestions[0].id === 'new') {
return 1;
}
return 0;
}}
filterSuggestions={(suggestions, value) => {
let filtered = suggestions.filter((suggestion, idx) => {
if (suggestion.id === 'new') {
return !value || value === '' || focusTransferPayees ? false : true;
}
return defaultFilterSuggestion(suggestion, value);
});
filtered.sort((p1, p2) => {
let r1 = p1.name.toLowerCase().startsWith(value.toLowerCase());
let r2 = p2.name.toLowerCase().startsWith(value.toLowerCase());
let r1exact = p1.name.toLowerCase() === value.toLowerCase();
let r2exact = p2.name.toLowerCase() === value.toLowerCase();
// (maniacal laughter) mwahaHAHAHAHAH
if (p1.id === 'new') {
return -1;
} else if (p2.id === 'new') {
return 1;
} else {
if (r1exact && !r2exact) {
return -1;
} else if (!r1exact && r2exact) {
return 1;
} else {
if (r1 === r2) {
return 0;
} else if (r1 && !r2) {
return -1;
} else {
return 1;
}
}
}
});
let isf = filtered.length > 100;
filtered = filtered.slice(0, 100);
filtered.filtered = isf;
if (filtered.length >= 2 && filtered[0].id === 'new') {
if (filtered[1].name.toLowerCase() === value.toLowerCase()) {
return filtered.slice(1);
}
}
return filtered;
}}
initialFilterSuggestions={suggestions => {
let filtered = false;
let res = suggestions.filter((suggestion, idx) => {
if (suggestion.id === 'new') {
// Never show the "create new" initially
return false;
}
if (idx >= 100 && !suggestion.transfer_acct) {
filtered = true;
return false;
}
return true;
});
if (filtered) {
res.filtered = true;
}
return res;
}}
renderItems={(items, getItemProps, highlightedIndex, inputValue) => (
{showMakeTransfer && (
{
onUpdate && onUpdate(null);
setFocusTransferPayees(!focusTransferPayees);
}}
/>
)}
{showManagePayees && (
onManagePayees()}
/>
)}
}
/>
)}
{...props}
/>
);
}