diff --git a/packages/desktop-client/src/components/payees/ManagePayees.tsx b/packages/desktop-client/src/components/payees/ManagePayees.tsx index 3144531d8cd..7bc630a1f9e 100644 --- a/packages/desktop-client/src/components/payees/ManagePayees.tsx +++ b/packages/desktop-client/src/components/payees/ManagePayees.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import memoizeOne from 'memoize-one'; import { getNormalisedString } from 'loot-core/src/shared/normalisation'; -import { groupById } from 'loot-core/src/shared/util'; +import { type Diff, groupById } from 'loot-core/src/shared/util'; import { type PayeeEntity } from 'loot-core/types/models'; import { @@ -70,10 +70,7 @@ type ManagePayeesProps = { ruleCounts: ComponentProps['ruleCounts']; orphanedPayees: PayeeEntity[]; initialSelectedIds: string[]; - onBatchChange: (arg: { - deleted?: Array<{ id: string }>; - updated?: Array<{ id: string; name?: string; favorite?: 0 | 1 }>; - }) => void; + onBatchChange: (diff: Diff) => void; onViewRules: ComponentProps['onViewRules']; onCreateRule: ComponentProps['onCreateRule']; onMerge: (ids: string[]) => Promise; @@ -126,7 +123,11 @@ export const ManagePayees = ({ ) => { const payee = payees.find(p => p.id === id); if (payee && payee[name] !== value) { - onBatchChange({ updated: [{ id, [name]: value }] }); + onBatchChange({ + updated: [{ id, [name]: value }], + added: [], + deleted: [], + }); } }, [payees, onBatchChange], @@ -141,6 +142,8 @@ export const ManagePayees = ({ function onDelete(ids?: { id: string }[]) { onBatchChange({ deleted: ids ?? [...selected.items].map(id => ({ id })), + updated: [], + added: [], }); if (!ids) selected.dispatch({ type: 'select-none' }); } @@ -152,10 +155,14 @@ export const ManagePayees = ({ if (allFavorited) { onBatchChange({ updated: [...selected.items].map(id => ({ id, favorite: 0 })), + added: [], + deleted: [], }); } else { onBatchChange({ updated: [...selected.items].map(id => ({ id, favorite: 1 })), + added: [], + deleted: [], }); } selected.dispatch({ type: 'select-none' }); diff --git a/packages/desktop-client/src/components/payees/ManagePayeesPage.jsx b/packages/desktop-client/src/components/payees/ManagePayeesPage.tsx similarity index 54% rename from packages/desktop-client/src/components/payees/ManagePayeesPage.jsx rename to packages/desktop-client/src/components/payees/ManagePayeesPage.tsx index 427195f7e72..c4bec9ffe46 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesPage.jsx +++ b/packages/desktop-client/src/components/payees/ManagePayeesPage.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { type PayeeEntity } from 'loot-core/types/models'; + import { Page } from '../Page'; import { ManagePayeesWithData } from './ManagePayeesWithData'; @@ -9,15 +11,14 @@ import { ManagePayeesWithData } from './ManagePayeesWithData'; export function ManagePayeesPage() { const { t } = useTranslation(); const location = useLocation(); + const locationState = location.state; + const initialSelectedIds = + locationState && 'selectedPayee' in locationState + ? [locationState.selectedPayee as PayeeEntity['id']] + : []; return ( - + ); } diff --git a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx b/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx similarity index 55% rename from packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx rename to packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx index 423a41132d3..064023fbad5 100644 --- a/packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx +++ b/packages/desktop-client/src/components/payees/ManagePayeesWithData.tsx @@ -1,59 +1,58 @@ -import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; - +import React, { useState, useEffect, useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; + +import { + getPayees, + initiallyLoadPayees, + pushModal, + setLastUndoState, +} from 'loot-core/client/actions'; +import { type UndoState } from 'loot-core/server/undo'; import { send, listen } from 'loot-core/src/platform/client/fetch'; -import { applyChanges } from 'loot-core/src/shared/util'; +import { applyChanges, type Diff } from 'loot-core/src/shared/util'; +import { type NewRuleEntity, type PayeeEntity } from 'loot-core/types/models'; -import { useActions } from '../../hooks/useActions'; -import { useCategories } from '../../hooks/useCategories'; import { usePayees } from '../../hooks/usePayees'; import { ManagePayees } from './ManagePayees'; -export function ManagePayeesWithData({ initialSelectedIds }) { - const initialPayees = usePayees(); - const lastUndoState = useSelector(state => state.app.lastUndoState); - const { grouped: categoryGroups } = useCategories(); +type ManagePayeesWithDataProps = { + initialSelectedIds: string[]; +}; - const { initiallyLoadPayees, getPayees, setLastUndoState, pushModal } = - useActions(); +export function ManagePayeesWithData({ + initialSelectedIds, +}: ManagePayeesWithDataProps) { + const payees = usePayees(); + const lastUndoState = useSelector(state => state.app.lastUndoState); + const dispatch = useDispatch(); - const [payees, setPayees] = useState(initialPayees); const [ruleCounts, setRuleCounts] = useState({ value: new Map() }); - const [orphans, setOrphans] = useState({ value: new Map() }); + const [orphans, setOrphans] = useState([]); - async function refetchOrphanedPayees() { + const refetchOrphanedPayees = useCallback(async () => { const orphs = await send('payees-get-orphaned'); setOrphans(orphs); - } + }, []); - async function refetchRuleCounts() { + const refetchRuleCounts = useCallback(async () => { let counts = await send('payees-get-rule-counts'); counts = new Map(Object.entries(counts)); setRuleCounts({ value: counts }); - } + }, []); useEffect(() => { async function loadData() { - const result = await initiallyLoadPayees(); - - // Wait a bit before setting the data. This lets the modal - // settle and makes for a smoother experience. - await new Promise(resolve => setTimeout(resolve, 100)); - - if (result) { - setPayees(result); - } - - refetchRuleCounts(); - refetchOrphanedPayees(); + await dispatch(initiallyLoadPayees()); + await refetchRuleCounts(); + await refetchOrphanedPayees(); } loadData(); const unlisten = listen('sync-event', async ({ type, tables }) => { if (type === 'applied') { if (tables.includes('rules')) { - refetchRuleCounts(); + await refetchRuleCounts(); } } }); @@ -61,40 +60,42 @@ export function ManagePayeesWithData({ initialSelectedIds }) { return () => { unlisten(); }; - }, []); + }, [dispatch, refetchRuleCounts, refetchOrphanedPayees]); - async function onUndo({ tables, messages, meta }) { - if (!tables.includes('payees') && !tables.includes('payee_mapping')) { - return; - } + useEffect(() => { + async function onUndo({ tables, messages, meta }: UndoState) { + if (!tables.includes('payees') && !tables.includes('payee_mapping')) { + return; + } - setPayees(await getPayees()); - refetchOrphanedPayees(); + await dispatch(getPayees()); + await refetchOrphanedPayees(); - if ( - (meta && meta.targetId) || - messages.find(msg => msg.dataset === 'rules') - ) { - refetchRuleCounts(); - } + const targetId = + meta && typeof meta === 'object' && 'targetId' in meta + ? meta.targetId + : null; - setLastUndoState(null); - } + if (targetId || messages.find(msg => msg.dataset === 'rules')) { + await refetchRuleCounts(); + } + + await dispatch(setLastUndoState(null)); + } - useEffect(() => { if (lastUndoState.current) { onUndo(lastUndoState.current); } return listen('undo-event', onUndo); - }, []); + }, [dispatch, lastUndoState, refetchRuleCounts, refetchOrphanedPayees]); - function onViewRules(id) { - pushModal('manage-rules', { payeeId: id }); + function onViewRules(id: PayeeEntity['id']) { + dispatch(pushModal('manage-rules', { payeeId: id })); } - function onCreateRule(id) { - const rule = { + function onCreateRule(id: PayeeEntity['id']) { + const rule: NewRuleEntity = { stage: null, conditionsOp: 'and', conditions: [ @@ -114,7 +115,7 @@ export function ManagePayeesWithData({ initialSelectedIds }) { }, ], }; - pushModal('edit-rule', { rule }); + dispatch(pushModal('edit-rule', { rule })); } return ( @@ -122,12 +123,10 @@ export function ManagePayeesWithData({ initialSelectedIds }) { payees={payees} ruleCounts={ruleCounts.value} orphanedPayees={orphans} - categoryGroups={categoryGroups} initialSelectedIds={initialSelectedIds} - lastUndoState={lastUndoState} - onBatchChange={changes => { - send('payees-batch-change', changes); - setPayees(applyChanges(changes, payees)); + onBatchChange={async (changes: Diff) => { + await send('payees-batch-change', changes); + await dispatch(getPayees()); setOrphans(applyChanges(changes, orphans)); }} onMerge={async ([targetId, ...mergeIds]) => { @@ -145,7 +144,6 @@ export function ManagePayeesWithData({ initialSelectedIds }) { } filtedOrphans = filtedOrphans.filter(o => !mergeIds.includes(o.id)); - const result = payees.filter(p => !mergeIds.includes(p.id)); mergeIds.forEach(id => { const count = ruleCounts.value.get(id) || 0; ruleCounts.value.set( @@ -154,7 +152,7 @@ export function ManagePayeesWithData({ initialSelectedIds }) { ); }); - setPayees(result); + await dispatch(getPayees()); setOrphans(filtedOrphans); setRuleCounts({ value: ruleCounts.value }); }} diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index 1ae73ef66fd..4afbc72a3e8 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -116,6 +116,7 @@ export interface ServerHandlers { }) => Promise; 'payees-check-orphaned': (arg: { ids }) => Promise; + 'payees-get-orphaned': () => Promise; 'payees-get-rules': (arg: { id: string }) => Promise; diff --git a/upcoming-release-notes/3867.md b/upcoming-release-notes/3867.md new file mode 100644 index 00000000000..ce526edc354 --- /dev/null +++ b/upcoming-release-notes/3867.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [joel-jeremy] +--- + +Convert ManagePayees page components to Typescript