Create mobile payees list page (#5767)

This commit is contained in:
Matiss Janis Aboltins
2025-09-22 18:56:57 +01:00
committed by GitHub
parent 7a4799de94
commit 5a888d44b9
8 changed files with 234 additions and 4 deletions

View File

@@ -17,7 +17,6 @@ import { GlobalKeys } from './GlobalKeys';
import { MobileNavTabs } from './mobile/MobileNavTabs';
import { TransactionEdit } from './mobile/transactions/TransactionEdit';
import { Notifications } from './Notifications';
import { ManagePayeesPage } from './payees/ManagePayeesPage';
import { Reports } from './reports';
import { LoadingIndicator } from './reports/LoadingIndicator';
import { NarrowAlternate, WideComponent } from './responsive';
@@ -267,7 +266,10 @@ export function FinancesApp() {
}
/>
<Route path="/payees" element={<ManagePayeesPage />} />
<Route
path="/payees"
element={<NarrowAlternate name="Payees" />}
/>
<Route
path="/rules"
element={<NarrowAlternate name="Rules" />}
@@ -346,6 +348,7 @@ export function FinancesApp() {
<Route path="/settings" element={<MobileNavTabs />} />
<Route path="/reports" element={<MobileNavTabs />} />
<Route path="/rules" element={<MobileNavTabs />} />
<Route path="/payees" element={<MobileNavTabs />} />
<Route path="*" element={null} />
</Routes>
</ScrollProvider>

View File

@@ -123,8 +123,8 @@ export function MobileNavTabs() {
Icon: SvgCalendar3,
},
{
name: t('Payees (Soon)'),
path: '/payees/soon',
name: t('Payees'),
path: '/payees',
style: navTabStyle,
Icon: SvgStoreFront,
},

View File

@@ -0,0 +1,73 @@
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { styles } from '@actual-app/components/styles';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { getNormalisedString } from 'loot-core/shared/normalisation';
import { type PayeeEntity } from 'loot-core/types/models';
import { PayeesList } from './PayeesList';
import { Search } from '@desktop-client/components/common/Search';
import { MobilePageHeader, Page } from '@desktop-client/components/Page';
import { usePayees } from '@desktop-client/hooks/usePayees';
import { useSelector } from '@desktop-client/redux';
export function MobilePayeesPage() {
const { t } = useTranslation();
const payees = usePayees();
const [filter, setFilter] = useState('');
const isLoading = useSelector(
s => s.payees.isPayeesLoading || s.payees.isCommonPayeesLoading,
);
const filteredPayees: PayeeEntity[] = useMemo(() => {
if (!filter) return payees;
const norm = getNormalisedString(filter);
return payees.filter(p => getNormalisedString(p.name).includes(norm));
}, [payees, filter]);
const onSearchChange = useCallback((value: string) => {
setFilter(value);
}, []);
const handlePayeePress = useCallback((_payee: PayeeEntity) => {
// Intentionally no-op for now
}, []);
return (
<Page header={<MobilePageHeader title={t('Payees')} />} padding={0}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.mobilePageBackground,
padding: 10,
width: '100%',
borderBottomWidth: 2,
borderBottomStyle: 'solid',
borderBottomColor: theme.tableBorder,
}}
>
<Search
placeholder={t('Filter payees…')}
value={filter}
onChange={onSearchChange}
width="100%"
height={styles.mobileMinHeight}
style={{
backgroundColor: theme.tableBackground,
borderColor: theme.formInputBorder,
}}
/>
</View>
<PayeesList
payees={filteredPayees}
isLoading={isLoading}
onPayeePress={handlePayeePress}
/>
</Page>
);
}

View File

@@ -0,0 +1,86 @@
import { Trans } from 'react-i18next';
import { AnimatedLoading } from '@actual-app/components/icons/AnimatedLoading';
import { Text } from '@actual-app/components/text';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { type PayeeEntity } from 'loot-core/types/models';
import { PayeesListItem } from './PayeesListItem';
import { MOBILE_NAV_HEIGHT } from '@desktop-client/components/mobile/MobileNavTabs';
type PayeesListProps = {
payees: PayeeEntity[];
isLoading?: boolean;
onPayeePress: (payee: PayeeEntity) => void;
};
export function PayeesList({
payees,
isLoading = false,
onPayeePress,
}: PayeesListProps) {
if (isLoading && payees.length === 0) {
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: 100,
}}
>
<AnimatedLoading style={{ width: 25, height: 25 }} />
</View>
);
}
if (payees.length === 0) {
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 20,
}}
>
<Text
style={{
fontSize: 16,
color: theme.pageTextSubdued,
textAlign: 'center',
}}
>
<Trans>No payees found.</Trans>
</Text>
</View>
);
}
return (
<View
style={{ flex: 1, paddingBottom: MOBILE_NAV_HEIGHT, overflow: 'auto' }}
>
{payees.map(payee => (
<PayeesListItem
key={payee.id}
payee={payee}
onPress={() => onPayeePress(payee)}
/>
))}
{isLoading && (
<View
style={{
alignItems: 'center',
paddingVertical: 20,
}}
>
<AnimatedLoading style={{ width: 20, height: 20 }} />
</View>
)}
</View>
);
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { Button } from '@actual-app/components/button';
import { SvgBookmark } from '@actual-app/components/icons/v1';
import { theme } from '@actual-app/components/theme';
import { type PayeeEntity } from 'loot-core/types/models';
type PayeesListItemProps = {
payee: PayeeEntity;
onPress: () => void;
};
export function PayeesListItem({ payee, onPress }: PayeesListItemProps) {
return (
<Button
variant="bare"
style={{
minHeight: 56,
width: '100%',
borderRadius: 0,
borderWidth: '0 0 1px 0',
borderColor: theme.tableBorder,
borderStyle: 'solid',
backgroundColor: theme.tableBackground,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
padding: '12px 16px',
gap: 5,
}}
onPress={onPress}
>
{payee.favorite && (
<SvgBookmark
width={15}
height={15}
style={{
color: theme.pageText,
flexShrink: 0,
}}
/>
)}
<span
style={{
fontSize: 15,
fontWeight: 500,
color: theme.pageText,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
title={payee.name}
>
{payee.name}
</span>
</Button>
);
}

View File

@@ -7,3 +7,4 @@ export { MobileRulesPage as Rules } from '../mobile/rules/MobileRulesPage';
export { MobileRuleEditPage as RuleEdit } from '../mobile/rules/MobileRuleEditPage';
export { CategoryPage as Category } from '../mobile/budget/CategoryPage';
export { MobilePayeesPage as Payees } from '../mobile/payees/MobilePayeesPage';

View File

@@ -9,6 +9,7 @@ export { Account } from '../accounts/Account';
export { ManageRulesPage as Rules } from '../ManageRulesPage';
export { ManageRulesPage as RuleEdit } from '../ManageRulesPage';
export { ManagePayeesPage as Payees } from '../payees/ManagePayeesPage';
export { UserDirectoryPage } from '../admin/UserDirectory/UserDirectoryPage';

View File

@@ -0,0 +1,7 @@
---
category: Features
authors: [MatissJanis]
---
Create a mobile payees list page with search and filtering capabilities.