diff --git a/packages/desktop-client/src/components/Modals.js b/packages/desktop-client/src/components/Modals.js index 894c340778749711016a1340497f561fa8fc1b76..eae0f072cbc9ee6662afac61831aecab218c2616 100644 --- a/packages/desktop-client/src/components/Modals.js +++ b/packages/desktop-client/src/components/Modals.js @@ -288,6 +288,7 @@ function Modals({ modalProps={modalProps} month={options.month} actions={actions} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} isGoalTemplatesEnabled={isGoalTemplatesEnabled} /> </Route> diff --git a/packages/desktop-client/src/components/accounts/TransactionsTable.js b/packages/desktop-client/src/components/accounts/TransactionsTable.js index 9a8980c8e8c6463b5764dcc4c65ff35245a2bc29..ee6f85fe2fd5ab7e235a5f4dcfa6a1aa5398fa78 100644 --- a/packages/desktop-client/src/components/accounts/TransactionsTable.js +++ b/packages/desktop-client/src/components/accounts/TransactionsTable.js @@ -37,7 +37,8 @@ import { titleFirst, } from 'loot-core/src/shared/util'; import LegacyAccountAutocomplete from 'loot-design/src/components/AccountAutocomplete'; -import CategoryAutocomplete from 'loot-design/src/components/CategorySelect'; +import NewCategoryAutocomplete from 'loot-design/src/components/CategoryAutocomplete'; +import LegacyCategoryAutocomplete from 'loot-design/src/components/CategorySelect'; import { View, Text, Tooltip, Button } from 'loot-design/src/components/common'; import DateSelect from 'loot-design/src/components/DateSelect'; import NewAccountAutocomplete from 'loot-design/src/components/NewAccountAutocomplete'; @@ -533,6 +534,9 @@ export const Transaction = React.memo(function Transaction(props) { const AccountAutocomplete = isNewAutocompleteEnabled ? NewAccountAutocomplete : LegacyAccountAutocomplete; + const CategoryAutocomplete = isNewAutocompleteEnabled + ? NewCategoryAutocomplete + : LegacyCategoryAutocomplete; let dispatchSelected = useSelectedDispatch(); diff --git a/packages/desktop-client/src/components/util/GenericInput.js b/packages/desktop-client/src/components/util/GenericInput.js index a6f6d88f20e60d9529ab1f687efecbcfec551cdc..b167af4dcafcda60683b360b31d2854c42865345 100644 --- a/packages/desktop-client/src/components/util/GenericInput.js +++ b/packages/desktop-client/src/components/util/GenericInput.js @@ -4,7 +4,8 @@ import { useSelector } from 'react-redux'; import { getMonthYearFormat } from 'loot-core/src/shared/months'; import LegacyAccountAutocomplete from 'loot-design/src/components/AccountAutocomplete'; import LegacyAutocomplete from 'loot-design/src/components/Autocomplete'; -import CategoryAutocomplete from 'loot-design/src/components/CategorySelect'; +import NewCategoryAutocomplete from 'loot-design/src/components/CategoryAutocomplete'; +import LegacyCategoryAutocomplete from 'loot-design/src/components/CategorySelect'; import { View, Input } from 'loot-design/src/components/common'; import DateSelect from 'loot-design/src/components/DateSelect'; import { Checkbox } from 'loot-design/src/components/forms'; @@ -33,6 +34,9 @@ export default function GenericInput({ const AccountAutocomplete = isNewAutocompleteEnabled ? NewAccountAutocomplete : LegacyAccountAutocomplete; + const CategoryAutocomplete = isNewAutocompleteEnabled + ? NewCategoryAutocomplete + : LegacyCategoryAutocomplete; let { payees, accounts, categoryGroups, dateFormat } = useSelector(state => { return { diff --git a/packages/loot-design/src/components/CategoryAutocomplete.js b/packages/loot-design/src/components/CategoryAutocomplete.js new file mode 100644 index 0000000000000000000000000000000000000000..77c8a70e26e147742ba5785d615e380ccd8bb854 --- /dev/null +++ b/packages/loot-design/src/components/CategoryAutocomplete.js @@ -0,0 +1,91 @@ +import React, { useMemo } from 'react'; +import { components as SelectComponents } from 'react-select'; + +import { colors } from '../style'; +import Split from '../svg/v0/Split'; + +import { View } from './common'; +import Autocomplete from './NewAutocomplete'; + +const SPLIT_TRANSACTION_KEY = 'split'; + +export default function CategoryAutocomplete({ + value, + categoryGroups, + showSplitOption = false, + multi = false, + onSplit, + ...props +}) { + const options = useMemo(() => { + const suggestions = categoryGroups.map(group => ({ + label: group.name, + options: group.categories.map(categ => ({ + value: categ.id, + label: categ.name, + })), + })); + + if (showSplitOption) { + suggestions.unshift({ + value: SPLIT_TRANSACTION_KEY, + label: SPLIT_TRANSACTION_KEY, + }); + } + + return suggestions; + }, [categoryGroups, showSplitOption]); + + const allOptions = useMemo( + () => + options.reduce( + (carry, { options }) => [...carry, ...(options || [])], + [], + ), + [options], + ); + + return ( + <Autocomplete + options={options} + value={ + multi + ? allOptions.filter(item => value.includes(item.value)) + : allOptions.find(item => item.value === value) + } + isMulti={multi} + components={{ + Option, + }} + {...props} + /> + ); +} + +function Option(props) { + if (props.value === SPLIT_TRANSACTION_KEY) { + return ( + <SelectComponents.Option {...props}> + <View + style={{ + flexDirection: 'row', + alignItems: 'center', + fontSize: 11, + color: colors.g8, + marginLeft: -12, + padding: '4px 0', + }} + data-testid="split-transaction-button" + > + <Split + width={10} + height={10} + style={{ marginRight: 5, color: 'inherit' }} + /> + Split Transaction + </View> + </SelectComponents.Option> + ); + } + return <SelectComponents.Option {...props} />; +} diff --git a/packages/loot-design/src/components/budget/index.js b/packages/loot-design/src/components/budget/index.js index 76b6115d0514b057f22ae8d0c3028aa9a7fd4960..7bc0f7395f0e0bdb2f6dfab1990ad9330c322988 100644 --- a/packages/loot-design/src/components/budget/index.js +++ b/packages/loot-design/src/components/budget/index.js @@ -1,4 +1,5 @@ import React, { useContext, useState, useMemo } from 'react'; +import { connect } from 'react-redux'; import * as monthUtils from 'loot-core/src/shared/months'; @@ -729,7 +730,11 @@ function ExpenseGroup({ ); } -function ExpenseCategory({ +const ExpenseCategory = connect(state => ({ + isNewAutocompleteEnabled: state.prefs.local['flags.newAutocomplete'], +}))(ExpenseCategoryInternal); + +function ExpenseCategoryInternal({ cat, budgetArray, editingCell, @@ -743,6 +748,7 @@ function ExpenseCategory({ onShowActivity, onDragChange, onReorder, + isNewAutocompleteEnabled, }) { let dragging = dragState && dragState.item === cat; @@ -801,6 +807,7 @@ function ExpenseCategory({ onEdit: onEditMonth, onBudgetAction, onShowActivity, + isNewAutocompleteEnabled, }} /> </View> diff --git a/packages/loot-design/src/components/budget/rollover/BudgetSummary.js b/packages/loot-design/src/components/budget/rollover/BudgetSummary.js index 2aa757e67d705a2bbcfc10774091f379b4018c0c..57a2408e065f02276bae2efbfbbaf050ac72d05e 100644 --- a/packages/loot-design/src/components/budget/rollover/BudgetSummary.js +++ b/packages/loot-design/src/components/budget/rollover/BudgetSummary.js @@ -134,7 +134,13 @@ function TotalsList({ prevMonthName, collapsed }) { ); } -function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) { +function ToBudget({ + month, + prevMonthName, + collapsed, + onBudgetAction, + isNewAutocompleteEnabled, +}) { return ( <SheetValue binding={rolloverBudget.toBudget} initialValue={0}> {node => { @@ -231,6 +237,7 @@ function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) { category, }); }} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} /> )} </View> @@ -243,7 +250,11 @@ function ToBudget({ month, prevMonthName, collapsed, onBudgetAction }) { ); } -export function BudgetSummary({ month, isGoalTemplatesEnabled }) { +export function BudgetSummary({ + month, + isGoalTemplatesEnabled, + isNewAutocompleteEnabled, +}) { let { currentMonth, summaryCollapsed: collapsed, @@ -405,13 +416,18 @@ export function BudgetSummary({ month, isGoalTemplatesEnabled }) { prevMonthName={prevMonthName} month={month} onBudgetAction={onBudgetAction} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} /> </View> ) : ( <> <TotalsList prevMonthName={prevMonthName} /> <View style={{ margin: '23px 0' }}> - <ToBudget month={month} onBudgetAction={onBudgetAction} /> + <ToBudget + month={month} + onBudgetAction={onBudgetAction} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} + /> </View> </> )} diff --git a/packages/loot-design/src/components/budget/rollover/TransferTooltip.js b/packages/loot-design/src/components/budget/rollover/TransferTooltip.js index 897419b777467995c292ea5f30e57fa1536a5b65..8a22460f74cf33fd84822632d31797634fd1e63f 100644 --- a/packages/loot-design/src/components/budget/rollover/TransferTooltip.js +++ b/packages/loot-design/src/components/budget/rollover/TransferTooltip.js @@ -3,7 +3,8 @@ import React, { useState, useContext, useEffect } from 'react'; import evalArithmetic from 'loot-core/src/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; -import CategoryAutocomplete from '../../CategorySelect'; +import NewCategoryAutocomplete from '../../CategoryAutocomplete'; +import LegacyCategoryAutocomplete from '../../CategorySelect'; import { View, Button, Tooltip, InitialFocus, Input } from '../../common'; import NamespaceContext from '../../spreadsheet/NamespaceContext'; import SpreadsheetContext from '../../spreadsheet/SpreadsheetContext'; @@ -16,7 +17,12 @@ export default function TransferTooltip({ tooltipProps, onSubmit, onClose, + isNewAutocompleteEnabled, }) { + const CategoryAutocomplete = isNewAutocompleteEnabled + ? NewCategoryAutocomplete + : LegacyCategoryAutocomplete; + let spreadsheet = useContext(SpreadsheetContext); let sheetName = useContext(NamespaceContext); let categoryGroups = useContext(CategoryGroupsContext); diff --git a/packages/loot-design/src/components/budget/rollover/rollover-components.js b/packages/loot-design/src/components/budget/rollover/rollover-components.js index 559bbf80641783830a2b83dc50476a0d69d59aa1..333f0ef55c71106bdd050aa79d5cc7a47f57353e 100644 --- a/packages/loot-design/src/components/budget/rollover/rollover-components.js +++ b/packages/loot-design/src/components/budget/rollover/rollover-components.js @@ -5,7 +5,8 @@ import evalArithmetic from 'loot-core/src/shared/arithmetic'; import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util'; import { styles, colors } from '../../../style'; -import CategoryAutocomplete from '../../CategorySelect'; +import NewCategoryAutocomplete from '../../CategoryAutocomplete'; +import LegacyCategoryAutocomplete from '../../CategorySelect'; import { View, Text, @@ -37,6 +38,7 @@ function CoverTooltip({ tooltipProps, onSubmit, onClose, + isNewAutocompleteEnabled, }) { let categoryGroups = useContext(CategoryGroupsContext); categoryGroups = addToBeBudgetedGroup( @@ -51,6 +53,10 @@ function CoverTooltip({ } } + const CategoryAutocomplete = isNewAutocompleteEnabled + ? NewCategoryAutocomplete + : LegacyCategoryAutocomplete; + return ( <Tooltip position="bottom-right" @@ -102,7 +108,13 @@ function CoverTooltip({ ); } -function BalanceTooltip({ categoryId, tooltip, monthIndex, onBudgetAction }) { +function BalanceTooltip({ + categoryId, + tooltip, + monthIndex, + onBudgetAction, + isNewAutocompleteEnabled, +}) { let carryover = useSheetValue(rolloverBudget.catCarryover(categoryId)); let balance = useSheetValue(rolloverBudget.catBalance(categoryId)); let [menu, setMenu] = useState('menu'); @@ -160,6 +172,7 @@ function BalanceTooltip({ categoryId, tooltip, monthIndex, onBudgetAction }) { to: toCategory, }); }} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} /> )} @@ -173,6 +186,7 @@ function BalanceTooltip({ categoryId, tooltip, monthIndex, onBudgetAction }) { from: fromCategory, }); }} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} /> )} </> @@ -292,6 +306,7 @@ export const ExpenseCategoryMonth = React.memo(function ExpenseCategoryMonth({ onEdit, onBudgetAction, onShowActivity, + isNewAutocompleteEnabled, }) { let borderColor = colors.border; let balanceTooltip = useTooltip(); @@ -385,6 +400,7 @@ export const ExpenseCategoryMonth = React.memo(function ExpenseCategoryMonth({ tooltip={balanceTooltip} monthIndex={monthIndex} onBudgetAction={onBudgetAction} + isNewAutocompleteEnabled={isNewAutocompleteEnabled} /> )} </Field> diff --git a/packages/loot-design/src/components/modals/EditField.js b/packages/loot-design/src/components/modals/EditField.js index 44d1ff40b81ca01e545ea98020baa3ce147d0256..53a4f6aca2c1c968a3ab51475505f2b4c7bc4c04 100644 --- a/packages/loot-design/src/components/modals/EditField.js +++ b/packages/loot-design/src/components/modals/EditField.js @@ -9,7 +9,8 @@ import { amountToInteger } from 'loot-core/src/shared/util'; import { colors } from '../../style'; import LegacyAccountAutocomplete from '../AccountAutocomplete'; -import CategoryAutocomplete from '../CategorySelect'; +import NewCategoryAutocomplete from '../CategoryAutocomplete'; +import LegacyCategoryAutocomplete from '../CategorySelect'; import { View, Modal, Input } from '../common'; import DateSelect from '../DateSelect'; import { SectionLabel } from '../forms'; @@ -56,6 +57,10 @@ function EditField({ ? NewAccountAutocomplete : LegacyAccountAutocomplete; + const CategoryAutocomplete = isNewAutocompleteEnabled + ? NewCategoryAutocomplete + : LegacyCategoryAutocomplete; + switch (name) { case 'date': { let today = currentDay(); diff --git a/packages/loot-design/src/components/modals/NordigenExternalMsg.js b/packages/loot-design/src/components/modals/NordigenExternalMsg.js index ae659b9bf95ad5b882de4ef9d928a05cecc78ca7..e71be87bbfcda619232e2006ce4cbf77659f0380 100644 --- a/packages/loot-design/src/components/modals/NordigenExternalMsg.js +++ b/packages/loot-design/src/components/modals/NordigenExternalMsg.js @@ -5,9 +5,9 @@ import { send } from 'loot-core/src/platform/client/fetch'; import { colors } from '../../style'; import AnimatedLoading from '../../svg/AnimatedLoading'; import { Error } from '../alerts'; -import Autocomplete from '../Autocomplete'; import { View, Modal, Button, P } from '../common'; import { FormField, FormLabel } from '../forms'; +import Autocomplete from '../NewAutocomplete'; import { COUNTRY_OPTIONS } from './countries'; @@ -27,7 +27,7 @@ function useAvailableBanks(country) { const results = await send('nordigen-get-banks', country); - setBanks(results); + setBanks(results.map(bank => ({ value: bank.id, label: bank.name }))); setIsLoading(false); } @@ -124,22 +124,12 @@ export default function NordigenExternalMsg({ <FormField style={{ marginBottom: 10 }}> <FormLabel title="Choose your country:" htmlFor="country-field" /> <Autocomplete - strict disabled={isConfigurationLoading} - suggestions={COUNTRY_OPTIONS} + options={COUNTRY_OPTIONS} onSelect={setCountry} - value={country} - inputProps={{ - id: 'country-field', - placeholder: '(please select)', - }} - renderItems={(items, getItemProps, highlightedIndex) => ( - <ItemList - items={items} - getItemProps={getItemProps} - highlightedIndex={highlightedIndex} - /> - )} + value={COUNTRY_OPTIONS.find(({ value }) => value === country)} + inputId="country-field" + placeholder="(please select)" /> </FormField> @@ -150,22 +140,12 @@ export default function NordigenExternalMsg({ <FormField> <FormLabel title="Choose your bank:" htmlFor="bank-field" /> <Autocomplete - strict focused - suggestions={bankOptions} + options={bankOptions} onSelect={setInstitutionId} - value={institutionId} - inputProps={{ - id: 'bank-field', - placeholder: '(please select)', - }} - renderItems={(items, getItemProps, highlightedIndex) => ( - <ItemList - items={items} - getItemProps={getItemProps} - highlightedIndex={highlightedIndex} - /> - )} + value={bankOptions.find(({ value }) => value === institutionId)} + inputId="bank-field" + placeholder="(please select)" /> </FormField> ))} @@ -255,36 +235,3 @@ export default function NordigenExternalMsg({ </Modal> ); } - -export function ItemList({ items, getItemProps, highlightedIndex }) { - return ( - <View - style={[ - { - overflow: 'auto', - padding: '5px 0', - maxHeight: 175, - }, - ]} - > - {items.map((item, idx) => ( - <div - key={item.id} - {...(getItemProps ? getItemProps({ item }) : null)} - style={{ - backgroundColor: - highlightedIndex === idx ? colors.n4 : 'transparent', - padding: 4, - paddingLeft: 20, - borderRadius: 0, - }} - data-testid={ - 'item' + (highlightedIndex === idx ? '-highlighted' : '') - } - > - {item.name} - </div> - ))} - </View> - ); -} diff --git a/packages/loot-design/src/components/modals/countries.js b/packages/loot-design/src/components/modals/countries.js index 91de293061541c04b6642212eab02a4e2fda0c7f..9277ec95c8adf0b184e69875c1b6d4c5f325829c 100644 --- a/packages/loot-design/src/components/modals/countries.js +++ b/packages/loot-design/src/components/modals/countries.js @@ -1,126 +1,126 @@ export const COUNTRY_OPTIONS = [ { - id: 'AT', - name: 'Austria', + value: 'AT', + label: 'Austria', }, { - id: 'BE', - name: 'Belgium', + value: 'BE', + label: 'Belgium', }, { - id: 'BG', - name: 'Bulgaria', + value: 'BG', + label: 'Bulgaria', }, { - id: 'HR', - name: 'Croatia', + value: 'HR', + label: 'Croatia', }, { - id: 'CY', - name: 'Cyprus', + value: 'CY', + label: 'Cyprus', }, { - id: 'CZ', - name: 'Czechia', + value: 'CZ', + label: 'Czechia', }, { - id: 'DK', - name: 'Denmark', + value: 'DK', + label: 'Denmark', }, { - id: 'EE', - name: 'Estonia', + value: 'EE', + label: 'Estonia', }, { - id: 'FI', - name: 'Finland', + value: 'FI', + label: 'Finland', }, { - id: 'FR', - name: 'France', + value: 'FR', + label: 'France', }, { - id: 'DE', - name: 'Germany', + value: 'DE', + label: 'Germany', }, { - id: 'GR', - name: 'Greece', + value: 'GR', + label: 'Greece', }, { - id: 'HU', - name: 'Hungary', + value: 'HU', + label: 'Hungary', }, { - id: 'IS', - name: 'Iceland', + value: 'IS', + label: 'Iceland', }, { - id: 'IE', - name: 'Ireland', + value: 'IE', + label: 'Ireland', }, { - id: 'IT', - name: 'Italy', + value: 'IT', + label: 'Italy', }, { - id: 'LV', - name: 'Latvia', + value: 'LV', + label: 'Latvia', }, { - id: 'LI', - name: 'Liechtenstein', + value: 'LI', + label: 'Liechtenstein', }, { - id: 'LT', - name: 'Lithuania', + value: 'LT', + label: 'Lithuania', }, { - id: 'LU', - name: 'Luxembourg', + value: 'LU', + label: 'Luxembourg', }, { - id: 'MT', - name: 'Malta', + value: 'MT', + label: 'Malta', }, { - id: 'NL', - name: 'Netherlands', + value: 'NL', + label: 'Netherlands', }, { - id: 'NO', - name: 'Norway', + value: 'NO', + label: 'Norway', }, { - id: 'PL', - name: 'Poland', + value: 'PL', + label: 'Poland', }, { - id: 'PT', - name: 'Portugal', + value: 'PT', + label: 'Portugal', }, { - id: 'RO', - name: 'Romania', + value: 'RO', + label: 'Romania', }, { - id: 'SK', - name: 'Slovakia', + value: 'SK', + label: 'Slovakia', }, { - id: 'SI', - name: 'Slovenia', + value: 'SI', + label: 'Slovenia', }, { - id: 'ES', - name: 'Spain', + value: 'ES', + label: 'Spain', }, { - id: 'SE', - name: 'Sweden', + value: 'SE', + label: 'Sweden', }, { - id: 'GB', - name: 'United Kingdom', + value: 'GB', + label: 'United Kingdom', }, ]; diff --git a/upcoming-release-notes/784.md b/upcoming-release-notes/784.md new file mode 100644 index 0000000000000000000000000000000000000000..4446470b44069d0002b8453a1b5d2bf797d98aec --- /dev/null +++ b/upcoming-release-notes/784.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Reafctor `Nordigen` and category Autocomplete to the new react-select component