mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Create mobile payees list page (#5767)
This commit is contained in:
committed by
GitHub
parent
7a4799de94
commit
5a888d44b9
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
7
upcoming-release-notes/5767.md
Normal file
7
upcoming-release-notes/5767.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Features
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
Create a mobile payees list page with search and filtering capabilities.
|
||||
|
||||
Reference in New Issue
Block a user