@pi-spi/app 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -5
- package/assets/icons/qr.png +0 -0
- package/assets/sounds/voice/search_prompt_en.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_fr.mp3 +0 -0
- package/assets/sounds/voice/search_prompt_pt.mp3 +0 -0
- package/example/app.json +9 -1
- package/lib/module/core/assets.js +1 -0
- package/lib/module/core/assets.js.map +1 -1
- package/lib/module/core/router.js +3 -0
- package/lib/module/core/router.js.map +1 -1
- package/lib/module/core/routes.js +14 -0
- package/lib/module/core/routes.js.map +1 -1
- package/lib/module/core/storage.js +27 -29
- package/lib/module/core/storage.js.map +1 -1
- package/lib/module/l10n/app_localizations.js.map +1 -1
- package/lib/module/l10n/app_localizations_en.js +21 -1
- package/lib/module/l10n/app_localizations_en.js.map +1 -1
- package/lib/module/l10n/app_localizations_fr.js +21 -1
- package/lib/module/l10n/app_localizations_fr.js.map +1 -1
- package/lib/module/l10n/app_localizations_pt.js +21 -1
- package/lib/module/l10n/app_localizations_pt.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js +3 -3
- package/lib/module/modules/alias/presentation/pages/alias_page_create_options.js.map +1 -1
- package/lib/module/modules/alias/presentation/pages/alias_page_otp.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js +2 -2
- package/lib/module/modules/categorie/presentation/pages/categorie_edit_icon_btn.js.map +1 -1
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js +12 -10
- package/lib/module/modules/categorie/presentation/pages/categorie_select_widget.js.map +1 -1
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js +14 -9
- package/lib/module/modules/compte/presentation/pages/solde_widget_with_qr.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_page.js +41 -2
- package/lib/module/modules/home/presentation/pages/home_page.js.map +1 -1
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js +10 -5
- package/lib/module/modules/home/presentation/pages/home_tab_compte_more_menu.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js +11 -4
- package/lib/module/modules/notification/presentation/bloc/notification_bloc.js.map +1 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js +7 -1
- package/lib/module/modules/notification/presentation/bloc/notification_state.js.map +1 -1
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js +6 -2
- package/lib/module/modules/notification/presentation/pages/notification_page_liste.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js +2 -2
- package/lib/module/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.js.map +1 -1
- package/lib/module/modules/profile/presentation/pages/web/profile_web_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page.js.map +1 -1
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js +2 -2
- package/lib/module/modules/qrcode/presentation/pages/qrcode_alias_page_share.js.map +1 -1
- package/lib/module/modules/search/domain/models/fonctionnalite.js +199 -0
- package/lib/module/modules/search/domain/models/fonctionnalite.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js +119 -0
- package/lib/module/modules/search/presentation/pages/search_fonctionnalites.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page.js +359 -0
- package/lib/module/modules/search/presentation/pages/search_page.js.map +1 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js +335 -0
- package/lib/module/modules/search/presentation/pages/search_page_voice_button.js.map +1 -0
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js +9 -4
- package/lib/module/modules/subscription/presentation/pages/subscription_menu_widget.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js +13 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.js.map +1 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js +7 -1
- package/lib/module/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js +216 -137
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js +6 -2
- package/lib/module/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.js.map +1 -1
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js +3 -3
- package/lib/module/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.js.map +1 -1
- package/lib/module/shared/widgets/amount_widget.js +4 -1
- package/lib/module/shared/widgets/amount_widget.js.map +1 -1
- package/lib/module/shared/widgets/input_amount.js.map +1 -1
- package/lib/module/shared/widgets/input_text.js +2 -0
- package/lib/module/shared/widgets/input_text.js.map +1 -1
- package/lib/module/shared/widgets/menu_actions_widget.js +10 -1
- package/lib/module/shared/widgets/menu_actions_widget.js.map +1 -1
- package/lib/module/shared/widgets/pagination_loader_widget.js +29 -0
- package/lib/module/shared/widgets/pagination_loader_widget.js.map +1 -0
- package/lib/typescript/src/core/assets.d.ts +1 -0
- package/lib/typescript/src/core/assets.d.ts.map +1 -1
- package/lib/typescript/src/core/router.d.ts +1 -0
- package/lib/typescript/src/core/router.d.ts.map +1 -1
- package/lib/typescript/src/core/routes.d.ts.map +1 -1
- package/lib/typescript/src/core/storage.d.ts +11 -3
- package/lib/typescript/src/core/storage.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations.d.ts +40 -0
- package/lib/typescript/src/l10n/app_localizations.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_en.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_fr.d.ts.map +1 -1
- package/lib/typescript/src/l10n/app_localizations_pt.d.ts.map +1 -1
- package/lib/typescript/src/modules/categorie/presentation/pages/categorie_select_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/compte/presentation/pages/solde_widget_with_qr.d.ts.map +1 -1
- package/lib/typescript/src/modules/home/presentation/pages/home_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/home/presentation/pages/home_tab_compte_more_menu.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts +5 -0
- package/lib/typescript/src/modules/notification/presentation/bloc/notification_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/notification/presentation/pages/notification_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/modules/profile/presentation/pages/web/profile_web_page.d.ts.map +1 -1
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts +70 -0
- package/lib/typescript/src/modules/search/domain/models/fonctionnalite.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_fonctionnalites.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts +21 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page.d.ts.map +1 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts +29 -0
- package/lib/typescript/src/modules/search/presentation/pages/search_page_voice_button.d.ts.map +1 -0
- package/lib/typescript/src/modules/subscription/presentation/pages/subscription_menu_widget.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts +5 -0
- package/lib/typescript/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts +5 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.d.ts.map +1 -1
- package/lib/typescript/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts +3 -1
- package/lib/typescript/src/shared/widgets/amount_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/input_text.d.ts +3 -3
- package/lib/typescript/src/shared/widgets/input_text.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts +5 -1
- package/lib/typescript/src/shared/widgets/menu_actions_widget.d.ts.map +1 -1
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts +7 -0
- package/lib/typescript/src/shared/widgets/pagination_loader_widget.d.ts.map +1 -0
- package/package.json +49 -35
- package/src/core/assets.ts +1 -0
- package/src/core/router.tsx +3 -0
- package/src/core/routes.tsx +15 -0
- package/src/core/storage.ts +30 -22
- package/src/l10n/app_localizations.ts +40 -0
- package/src/l10n/app_localizations_en.ts +20 -0
- package/src/l10n/app_localizations_fr.ts +20 -0
- package/src/l10n/app_localizations_pt.ts +20 -0
- package/src/modules/alias/presentation/pages/alias_page_create_options.tsx +3 -3
- package/src/modules/alias/presentation/pages/alias_page_otp.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_edit_icon_btn.tsx +2 -2
- package/src/modules/categorie/presentation/pages/categorie_select_widget.tsx +13 -9
- package/src/modules/compte/presentation/pages/solde_widget_with_qr.tsx +10 -3
- package/src/modules/home/presentation/pages/home_page.tsx +31 -2
- package/src/modules/home/presentation/pages/home_tab_compte_more_menu.tsx +10 -5
- package/src/modules/notification/presentation/bloc/notification_bloc.ts +12 -3
- package/src/modules/notification/presentation/bloc/notification_state.ts +11 -1
- package/src/modules/notification/presentation/pages/notification_page_liste.tsx +4 -1
- package/src/modules/profile/presentation/pages/parametre/profile_parametre_page_notifications.tsx +2 -2
- package/src/modules/profile/presentation/pages/web/profile_web_page.tsx +3 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page.tsx +2 -2
- package/src/modules/qrcode/presentation/pages/qrcode_alias_page_share.tsx +2 -2
- package/src/modules/search/domain/models/fonctionnalite.ts +217 -0
- package/src/modules/search/presentation/pages/search_fonctionnalites.ts +135 -0
- package/src/modules/search/presentation/pages/search_page.tsx +314 -0
- package/src/modules/search/presentation/pages/search_page_voice_button.tsx +283 -0
- package/src/modules/subscription/presentation/pages/subscription_menu_widget.tsx +9 -4
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_bloc.ts +15 -1
- package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts +10 -1
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_filters.tsx +204 -101
- package/src/modules/transactions/presentation/pages/transaction_search/transaction_search_page_liste.tsx +4 -1
- package/src/modules/transactions/presentation/pages/transaction_send/transaction_send_page_options.tsx +3 -3
- package/src/shared/widgets/amount_widget.tsx +5 -2
- package/src/shared/widgets/input_amount.tsx +2 -2
- package/src/shared/widgets/input_text.tsx +8 -3
- package/src/shared/widgets/menu_actions_widget.tsx +8 -2
- package/src/shared/widgets/pagination_loader_widget.tsx +24 -0
package/src/modules/transactions/presentation/bloc/transaction_search/transaction_search_state.ts
CHANGED
|
@@ -12,4 +12,13 @@ export class TransactionSearchInitialState extends TransactionSearchState {}
|
|
|
12
12
|
|
|
13
13
|
export class TransactionSearchLoadingState extends TransactionSearchState {}
|
|
14
14
|
|
|
15
|
-
export class TransactionSearchListState extends TransactionSearchState {
|
|
15
|
+
export class TransactionSearchListState extends TransactionSearchState {
|
|
16
|
+
constructor(
|
|
17
|
+
transactions: TransactionListe,
|
|
18
|
+
command: TransactionSearchCommand,
|
|
19
|
+
/** true pendant le chargement de la page suivante : un petit loader est affiché en bas de la liste. */
|
|
20
|
+
readonly isPaginating = false,
|
|
21
|
+
) {
|
|
22
|
+
super(transactions, command);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,80 +1,154 @@
|
|
|
1
1
|
import { MaterialIcons } from '@expo/vector-icons';
|
|
2
|
-
import {
|
|
3
|
-
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Platform, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
|
|
6
|
-
import { useBloc
|
|
6
|
+
import { useBloc } from '../../../../../core/bloc_react';
|
|
7
7
|
import { AppRouter, useAppRouter } from '../../../../../core/router';
|
|
8
8
|
import { Themer, useTheme } from '../../../../../core/theme';
|
|
9
9
|
import type { AppLocalizations } from '../../../../../l10n/app_localizations';
|
|
10
10
|
import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
11
|
-
import { TextButton } from '../../../../../shared/material/buttons';
|
|
11
|
+
import { ElevatedButton, TextButton } from '../../../../../shared/material/buttons';
|
|
12
12
|
import { Card } from '../../../../../shared/material/card';
|
|
13
13
|
import { DateFormat } from '../../../../../shared/utils/date_format';
|
|
14
14
|
import { showSelectDateRange } from '../../../../../shared/widgets/select_date_widget';
|
|
15
15
|
import { CategorieSelectWidget } from '../../../../categorie/presentation/pages/categorie_select_widget';
|
|
16
16
|
import { TransactionSens } from '../../../domain/models/transaction';
|
|
17
|
-
import type { TransactionSearchCommand } from '../../../domain/models/transaction_search/transaction_search_command';
|
|
18
17
|
import { TransactionSearchBloc } from '../../bloc/transaction_search/transaction_search_bloc';
|
|
19
18
|
|
|
20
|
-
/**
|
|
19
|
+
/** Sens par défaut : reçus et envoyés */
|
|
20
|
+
const SENS_PAR_DEFAUT: readonly TransactionSens[] = [TransactionSens.credit, TransactionSens.debit];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Filtres de la recherche : plage de dates, sens (reçus / envoyés) et
|
|
24
|
+
* catégories. Les choix sont faits sur un brouillon : ils ne sont reportés
|
|
25
|
+
* sur la commande qu'avec « Appliquer ». Fermer la page les abandonne.
|
|
26
|
+
*/
|
|
21
27
|
export function TransactionSearchPageFilters() {
|
|
22
28
|
const traductions = useAppLocalizations();
|
|
23
29
|
const theme = useTheme();
|
|
24
30
|
const router = useAppRouter();
|
|
25
31
|
const insets = useSafeAreaInsets();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const [,
|
|
32
|
+
const command = useBloc(TransactionSearchBloc).state.command;
|
|
33
|
+
|
|
34
|
+
// Brouillon des filtres
|
|
35
|
+
const [dateDebut, setDateDebut] = useState(command.filters.dateDebut);
|
|
36
|
+
const [dateFin, setDateFin] = useState(command.filters.dateFin);
|
|
37
|
+
const [sens, setSens] = useState<TransactionSens[]>(() => [...command.filters.sens]);
|
|
38
|
+
const [categories, setCategories] = useState<string[]>(() => [...command.filters.categories]);
|
|
39
|
+
// Change à chaque réinitialisation : les listes de catégories gardent leur
|
|
40
|
+
// sélection en interne, il faut les recréer pour la vider.
|
|
41
|
+
const [versionCategories, setVersionCategories] = useState(0);
|
|
42
|
+
|
|
43
|
+
const estModifie =
|
|
44
|
+
dateDebut != null ||
|
|
45
|
+
dateFin != null ||
|
|
46
|
+
categories.length > 0 ||
|
|
47
|
+
sens.length !== SENS_PAR_DEFAUT.length ||
|
|
48
|
+
!SENS_PAR_DEFAUT.every((s) => sens.includes(s));
|
|
49
|
+
|
|
50
|
+
const reinitialiser = () => {
|
|
51
|
+
setDateDebut(undefined);
|
|
52
|
+
setDateFin(undefined);
|
|
53
|
+
setSens([...SENS_PAR_DEFAUT]);
|
|
54
|
+
setCategories([]);
|
|
55
|
+
setVersionCategories((v) => v + 1);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const appliquer = () => {
|
|
59
|
+
command.filters.dateDebut = dateDebut;
|
|
60
|
+
command.filters.dateFin = dateFin;
|
|
61
|
+
command.filters.sens = [...sens];
|
|
62
|
+
command.filters.categories = [...categories];
|
|
63
|
+
AppRouter.pop(router, { value: command });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const separateurColor = theme.brightness === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)';
|
|
30
67
|
|
|
31
68
|
return (
|
|
32
69
|
<View style={[styles.flex, { paddingBottom: insets.bottom }]}>
|
|
33
|
-
{/* Barre :
|
|
70
|
+
{/* Barre : fermer, titre, réinitialiser */}
|
|
34
71
|
<View style={styles.bar}>
|
|
35
|
-
<Pressable accessibilityRole="button" hitSlop={12} style={styles.
|
|
36
|
-
<MaterialIcons name="
|
|
72
|
+
<Pressable accessibilityRole="button" hitSlop={12} style={styles.close} onPress={() => AppRouter.pop(router)}>
|
|
73
|
+
<MaterialIcons name="close" size={24} color={theme.colorScheme.onSurface} />
|
|
37
74
|
</Pressable>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
75
|
+
<Text style={[theme.textTheme.headlineMedium, styles.title]}>{traductions.transactionSearchInputFilterTitle}</Text>
|
|
76
|
+
<TextButton onPress={estModifie ? reinitialiser : undefined}>
|
|
77
|
+
{traductions.transactionSearchInputFilterBtnReinitialiser}
|
|
78
|
+
</TextButton>
|
|
41
79
|
</View>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
{/* Séparateur discret */}
|
|
81
|
+
<View style={[styles.separateur, { backgroundColor: separateurColor }]} />
|
|
82
|
+
|
|
83
|
+
{/* Filtres */}
|
|
84
|
+
<ScrollView style={styles.flex} contentContainerStyle={styles.content}>
|
|
85
|
+
<SectionTitle label={traductions.transactionSearchInputFilterDateTitle} />
|
|
86
|
+
<FiltersDates
|
|
87
|
+
dateDebut={dateDebut}
|
|
88
|
+
dateFin={dateFin}
|
|
89
|
+
traductions={traductions}
|
|
90
|
+
onChange={(debut, fin) => {
|
|
91
|
+
setDateDebut(debut);
|
|
92
|
+
setDateFin(fin);
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
<View style={styles.gap28} />
|
|
96
|
+
{/* Sens de l'opération, puis catégories */}
|
|
97
|
+
<SectionTitle label={traductions.transactionSearchInputFilterCategoriesTitle} />
|
|
98
|
+
<FiltersSens selection={sens} traductions={traductions} separateurColor={separateurColor} onChange={setSens} />
|
|
99
|
+
<View style={styles.gap16} />
|
|
100
|
+
<CategorieSelectWidget
|
|
101
|
+
key={versionCategories}
|
|
102
|
+
selectedCategories={categories}
|
|
103
|
+
onSelectionChanged={(categorie, checked) => {
|
|
104
|
+
setCategories((c) => {
|
|
105
|
+
const autres = c.filter((id) => id !== categorie.id);
|
|
106
|
+
return checked ? [...autres, categorie.id] : autres;
|
|
107
|
+
});
|
|
108
|
+
}}
|
|
109
|
+
/>
|
|
110
|
+
</ScrollView>
|
|
111
|
+
|
|
112
|
+
{/* Appliquer */}
|
|
113
|
+
<View style={styles.footer}>
|
|
114
|
+
<ElevatedButton onPress={appliquer}>{traductions.transactionSearchInputFilterBtnAppliquer}</ElevatedButton>
|
|
68
115
|
</View>
|
|
69
116
|
</View>
|
|
70
117
|
);
|
|
71
118
|
}
|
|
72
119
|
|
|
73
|
-
|
|
120
|
+
/** Titre d'une section de filtres */
|
|
121
|
+
function SectionTitle({ label }: { label: string }) {
|
|
122
|
+
const theme = useTheme();
|
|
123
|
+
return <Text style={[theme.textTheme.displayMedium, styles.sectionTitle]}>{label.toUpperCase()}</Text>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Icône dans un carré arrondi */
|
|
127
|
+
function IconBox({ name, color, backgroundColor }: { name: keyof typeof MaterialIcons.glyphMap; color: string; backgroundColor: string }) {
|
|
128
|
+
return (
|
|
129
|
+
<View style={[styles.icon, { backgroundColor }]}>
|
|
130
|
+
<MaterialIcons name={name} size={22} color={color} />
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function FiltersDates({
|
|
136
|
+
dateDebut,
|
|
137
|
+
dateFin,
|
|
138
|
+
traductions,
|
|
139
|
+
onChange,
|
|
140
|
+
}: {
|
|
141
|
+
dateDebut?: Date;
|
|
142
|
+
dateFin?: Date;
|
|
143
|
+
traductions: AppLocalizations;
|
|
144
|
+
onChange: (debut?: Date, fin?: Date) => void;
|
|
145
|
+
}) {
|
|
74
146
|
const theme = useTheme();
|
|
75
147
|
const router = useAppRouter();
|
|
76
|
-
const format = new DateFormat('d MMM', traductions.localeName);
|
|
77
|
-
const
|
|
148
|
+
const format = new DateFormat('d MMM y', traductions.localeName);
|
|
149
|
+
const estDefinie = dateDebut != null;
|
|
150
|
+
const plage = estDefinie ? (dateFin != null ? `${format.format(dateDebut)} – ${format.format(dateFin)}` : format.format(dateDebut)) : null;
|
|
151
|
+
|
|
78
152
|
return (
|
|
79
153
|
<Card>
|
|
80
154
|
<Pressable
|
|
@@ -82,64 +156,90 @@ function FiltersDates({ command, traductions, onChange }: { command: Transaction
|
|
|
82
156
|
style={styles.tile}
|
|
83
157
|
onPress={async () => {
|
|
84
158
|
const dates = await showSelectDateRange(router, { title: traductions.transactionSearchInputFilterDateSelectTitle });
|
|
85
|
-
|
|
86
|
-
command.filters.dateFin = dates.length === 2 ? dates[1] : undefined;
|
|
87
|
-
onChange();
|
|
159
|
+
onChange(dates[0], dates.length === 2 ? dates[1] : undefined);
|
|
88
160
|
}}
|
|
89
161
|
>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
162
|
+
{/* Teinté quand le filtre est actif */}
|
|
163
|
+
<IconBox
|
|
164
|
+
name="calendar-month"
|
|
165
|
+
color={estDefinie ? Themer.primaryDark : Themer.neural04Color}
|
|
166
|
+
backgroundColor={estDefinie ? 'rgba(242,169,0,0.15)' : theme.colorScheme.surface}
|
|
167
|
+
/>
|
|
168
|
+
{/* La plage choisie, sinon l'invitation à la choisir */}
|
|
169
|
+
<Text style={[theme.textTheme.headlineSmall, styles.label15, styles.texts]}>
|
|
170
|
+
{plage ?? traductions.transactionSearchInputFilterDateSubTitle}
|
|
171
|
+
</Text>
|
|
172
|
+
{/* Effacer la plage, ou ouvrir le sélecteur */}
|
|
173
|
+
{estDefinie ? (
|
|
174
|
+
<Pressable accessibilityRole="button" hitSlop={8} style={styles.trailing} onPress={() => onChange(undefined, undefined)}>
|
|
175
|
+
<MaterialIcons name="close" size={20} color={Themer.neural03Color} />
|
|
176
|
+
</Pressable>
|
|
177
|
+
) : (
|
|
178
|
+
<MaterialIcons
|
|
179
|
+
style={styles.trailing}
|
|
180
|
+
name={Platform.OS === 'ios' ? 'arrow-forward-ios' : 'arrow-forward'}
|
|
181
|
+
size={Platform.OS === 'ios' ? 18 : 22}
|
|
182
|
+
color={Themer.neural03Color}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
105
185
|
</Pressable>
|
|
106
186
|
</Card>
|
|
107
187
|
);
|
|
108
188
|
}
|
|
109
189
|
|
|
110
|
-
|
|
190
|
+
/** Mêmes icônes et couleurs que dans la liste des transactions. */
|
|
191
|
+
function FiltersSens({
|
|
192
|
+
selection,
|
|
193
|
+
traductions,
|
|
194
|
+
separateurColor,
|
|
195
|
+
onChange,
|
|
196
|
+
}: {
|
|
197
|
+
selection: TransactionSens[];
|
|
198
|
+
traductions: AppLocalizations;
|
|
199
|
+
separateurColor: string;
|
|
200
|
+
onChange: (sens: TransactionSens[]) => void;
|
|
201
|
+
}) {
|
|
111
202
|
const theme = useTheme();
|
|
112
203
|
const items = [
|
|
113
|
-
{
|
|
114
|
-
|
|
204
|
+
{
|
|
205
|
+
sens: TransactionSens.credit,
|
|
206
|
+
icon: 'arrow-forward' as const,
|
|
207
|
+
color: Themer.successColor,
|
|
208
|
+
fond: 'rgba(70,169,61,0.12)',
|
|
209
|
+
label: traductions.transactionSearchInputFilterCategoriesSensRecus,
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
sens: TransactionSens.debit,
|
|
213
|
+
icon: 'arrow-back' as const,
|
|
214
|
+
color: theme.colorScheme.error,
|
|
215
|
+
fond: 'rgba(250,90,37,0.12)',
|
|
216
|
+
label: traductions.transactionSearchInputFilterCategoriesSensPayes,
|
|
217
|
+
},
|
|
115
218
|
];
|
|
116
219
|
return (
|
|
117
220
|
<Card>
|
|
118
|
-
{items.map((item) => {
|
|
119
|
-
const checked =
|
|
221
|
+
{items.map((item, index) => {
|
|
222
|
+
const checked = selection.includes(item.sens);
|
|
120
223
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
</View>
|
|
141
|
-
<Text style={[theme.textTheme.headlineSmall, styles.label]}>{item.label}</Text>
|
|
142
|
-
</Pressable>
|
|
224
|
+
<View key={item.sens}>
|
|
225
|
+
{/* Séparateur discret */}
|
|
226
|
+
{index > 0 && <View style={[styles.separateur, styles.separateurIndent, { backgroundColor: separateurColor }]} />}
|
|
227
|
+
<Pressable
|
|
228
|
+
accessibilityRole="checkbox"
|
|
229
|
+
accessibilityState={{ checked }}
|
|
230
|
+
style={styles.tile}
|
|
231
|
+
onPress={() => onChange(checked ? selection.filter((s) => s !== item.sens) : [...selection, item.sens])}
|
|
232
|
+
>
|
|
233
|
+
<IconBox name={item.icon} color={item.color} backgroundColor={item.fond} />
|
|
234
|
+
<Text style={[theme.textTheme.headlineSmall, styles.label15, styles.texts]}>{item.label}</Text>
|
|
235
|
+
<MaterialIcons
|
|
236
|
+
style={styles.trailing}
|
|
237
|
+
name={checked ? 'check-box' : 'check-box-outline-blank'}
|
|
238
|
+
size={24}
|
|
239
|
+
color={checked ? theme.colorScheme.primary : Themer.neural02Color}
|
|
240
|
+
/>
|
|
241
|
+
</Pressable>
|
|
242
|
+
</View>
|
|
143
243
|
);
|
|
144
244
|
})}
|
|
145
245
|
</Card>
|
|
@@ -148,16 +248,19 @@ function FiltersSens({ command, traductions, onChange }: { command: TransactionS
|
|
|
148
248
|
|
|
149
249
|
const styles = StyleSheet.create({
|
|
150
250
|
flex: { flex: 1 },
|
|
151
|
-
bar: { flexDirection: 'row', alignItems: 'center',
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
251
|
+
bar: { flexDirection: 'row', alignItems: 'center', paddingTop: 8, paddingLeft: 4, paddingRight: 8 },
|
|
252
|
+
close: { padding: 12 },
|
|
253
|
+
title: { flex: 1, textAlign: 'center' },
|
|
254
|
+
separateur: { height: StyleSheet.hairlineWidth },
|
|
255
|
+
separateurIndent: { marginLeft: 76, marginRight: 16 },
|
|
256
|
+
content: { paddingHorizontal: 16, paddingTop: 24, paddingBottom: 16 },
|
|
257
|
+
sectionTitle: { color: Themer.neural03Color, letterSpacing: 0.6, marginLeft: 4, marginBottom: 10 },
|
|
258
|
+
tile: { flexDirection: 'row', alignItems: 'center', paddingLeft: 16, paddingRight: 8, paddingVertical: 10 },
|
|
259
|
+
icon: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center' },
|
|
157
260
|
texts: { flex: 1, marginHorizontal: 16 },
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
gap16: {
|
|
162
|
-
|
|
261
|
+
label15: { fontSize: 15 },
|
|
262
|
+
trailing: { marginRight: 8 },
|
|
263
|
+
footer: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 16 },
|
|
264
|
+
gap16: { height: 16 },
|
|
265
|
+
gap28: { height: 28 },
|
|
163
266
|
});
|
|
@@ -7,6 +7,7 @@ import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
|
7
7
|
import { Card } from '../../../../../shared/material/card';
|
|
8
8
|
import { DateFormat } from '../../../../../shared/utils/date_format';
|
|
9
9
|
import { AmountWidget } from '../../../../../shared/widgets/amount_widget';
|
|
10
|
+
import { PaginationLoaderWidget } from '../../../../../shared/widgets/pagination_loader_widget';
|
|
10
11
|
import { TransactionSens, type Transaction } from '../../../domain/models/transaction';
|
|
11
12
|
import { TransactionSearchBloc } from '../../bloc/transaction_search/transaction_search_bloc';
|
|
12
13
|
import { TransactionSearchListEvent, TransactionSearchPaginateEvent } from '../../bloc/transaction_search/transaction_search_event';
|
|
@@ -89,11 +90,13 @@ export function TransactionSearchPageListe({
|
|
|
89
90
|
if (command.canLoadMore()) bloc.add(new TransactionSearchPaginateEvent(command.index, command));
|
|
90
91
|
}}
|
|
91
92
|
onEndReachedThreshold={0.2}
|
|
93
|
+
// Chargement de la page suivante
|
|
94
|
+
ListFooterComponent={state.isPaginating ? <PaginationLoaderWidget /> : undefined}
|
|
92
95
|
renderItem={({ item }) => (
|
|
93
96
|
<View>
|
|
94
97
|
{/* Jour et bilan */}
|
|
95
98
|
<View style={styles.header}>
|
|
96
|
-
<Text style={headerStyle}>{new DateFormat('d MMMM', traductions.localeName).format(new Date(`${item.jour}T00:00:00`))}</Text>
|
|
99
|
+
<Text style={headerStyle}>{new DateFormat('d MMMM y', traductions.localeName).format(new Date(`${item.jour}T00:00:00`))}</Text>
|
|
97
100
|
<AmountWidget montant={item.bilanOperation} style={headerStyle} />
|
|
98
101
|
</View>
|
|
99
102
|
<View style={styles.gap8} />
|
|
@@ -4,7 +4,7 @@ import { Images } from '../../../../../core/assets';
|
|
|
4
4
|
import { useBloc } from '../../../../../core/bloc_react';
|
|
5
5
|
import { AppRouter, useAppRouter } from '../../../../../core/router';
|
|
6
6
|
import { useAppLocalizations } from '../../../../../l10n/localizations';
|
|
7
|
-
import { MenuActionItem,
|
|
7
|
+
import { MenuActionItem, MenuActionsWidget } from '../../../../../shared/widgets/menu_actions_widget';
|
|
8
8
|
import { AliasBloc } from '../../../../alias/presentation/bloc/alias_bloc';
|
|
9
9
|
import { ContactPI } from '../../../../contacts/domain/contact_pi';
|
|
10
10
|
import { TransactionCanal } from '../../../domain/models/transaction_canal';
|
|
@@ -66,12 +66,12 @@ export function TransactionSendPageOptions({ action }: { action: string }) {
|
|
|
66
66
|
|
|
67
67
|
return (
|
|
68
68
|
<View>
|
|
69
|
-
<
|
|
69
|
+
<MenuActionsWidget items={items} />
|
|
70
70
|
{Platform.OS !== 'web' && (
|
|
71
71
|
<>
|
|
72
72
|
<View style={{ height: 5 }} />
|
|
73
73
|
{/* Nouveau contact */}
|
|
74
|
-
<
|
|
74
|
+
<MenuActionsWidget
|
|
75
75
|
items={[
|
|
76
76
|
new MenuActionItem(
|
|
77
77
|
Images.iconTransactionSendByNewContact,
|
|
@@ -22,6 +22,7 @@ export function AmountWidget({
|
|
|
22
22
|
surfixText,
|
|
23
23
|
style,
|
|
24
24
|
numberOfLines,
|
|
25
|
+
adjustsFontSizeToFit,
|
|
25
26
|
}: {
|
|
26
27
|
montant: number | null | undefined;
|
|
27
28
|
sign?: string;
|
|
@@ -29,6 +30,8 @@ export function AmountWidget({
|
|
|
29
30
|
surfixText?: string;
|
|
30
31
|
style?: TextStyle;
|
|
31
32
|
numberOfLines?: number;
|
|
33
|
+
/** Réduit la police pour que le texte tienne dans la largeur disponible (avec [numberOfLines]). */
|
|
34
|
+
adjustsFontSizeToFit?: boolean;
|
|
32
35
|
}) {
|
|
33
36
|
const locale = useAppLocalizations().localeName;
|
|
34
37
|
const configBloc = useBloc(ConfigBloc);
|
|
@@ -45,7 +48,7 @@ export function AmountWidget({
|
|
|
45
48
|
const text = `${prefix}${value}${suffix}`;
|
|
46
49
|
if (displayAmount) {
|
|
47
50
|
return (
|
|
48
|
-
<Text numberOfLines={numberOfLines} style={style}>
|
|
51
|
+
<Text numberOfLines={numberOfLines} adjustsFontSizeToFit={adjustsFontSizeToFit} style={style}>
|
|
49
52
|
{text}
|
|
50
53
|
</Text>
|
|
51
54
|
);
|
|
@@ -56,7 +59,7 @@ export function AmountWidget({
|
|
|
56
59
|
// transparent, d'où le flou porté par la vue.
|
|
57
60
|
return (
|
|
58
61
|
<View style={styles.hidden}>
|
|
59
|
-
<Text numberOfLines={numberOfLines} style={[style, styles.transparent]}>
|
|
62
|
+
<Text numberOfLines={numberOfLines} adjustsFontSizeToFit={adjustsFontSizeToFit} style={[style, styles.transparent]}>
|
|
60
63
|
{text}
|
|
61
64
|
</Text>
|
|
62
65
|
</View>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRef, useState, type ReactNode } from 'react';
|
|
2
|
-
import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native';
|
|
2
|
+
import { Pressable, StyleSheet, Text, TextInput, View, type TextInputInstance } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { Themer, useTheme } from '../../core/theme';
|
|
5
5
|
import { useAppLocalizations } from '../../l10n/localizations';
|
|
@@ -60,7 +60,7 @@ export function InputAmount({
|
|
|
60
60
|
const traductions = useAppLocalizations();
|
|
61
61
|
const locale = traductions.localeName;
|
|
62
62
|
const input = theme.inputDecorationTheme;
|
|
63
|
-
const ref = useRef<
|
|
63
|
+
const ref = useRef<TextInputInstance>(null);
|
|
64
64
|
const [focused, setFocused] = useState(false);
|
|
65
65
|
const [text, setText] = useState(() => formatSaisie(value ?? '', locale));
|
|
66
66
|
const hasError = messageError != null && messageError.length > 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { forwardRef, useRef, useState, type ReactNode } from 'react';
|
|
2
|
-
import { StyleSheet, Text, TextInput, View, type KeyboardTypeOptions, type ReturnKeyTypeOptions } from 'react-native';
|
|
1
|
+
import { forwardRef, useRef, useState, type ForwardRefExoticComponent, type ReactNode, type RefAttributes } from 'react';
|
|
2
|
+
import { StyleSheet, Text, TextInput, View, type KeyboardTypeOptions, type ReturnKeyTypeOptions, type TextInputInstance } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { useTheme } from '../../core/theme';
|
|
5
5
|
import { FloatingLabel } from './floating_label';
|
|
@@ -37,7 +37,12 @@ const HEIGHT = 56;
|
|
|
37
37
|
* comme un `TextField` Material (le texte d'aide n'apparaît qu'au focus
|
|
38
38
|
* quand il y a un label), et message d'erreur sous le champ.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
// Type annoté : celui inféré depuis React Native 0.88 n'est pas exportable
|
|
41
|
+
// dans les déclarations du package.
|
|
42
|
+
export const CustomTextInput: ForwardRefExoticComponent<CustomTextInputProps & RefAttributes<TextInputInstance>> = forwardRef<
|
|
43
|
+
TextInputInstance,
|
|
44
|
+
CustomTextInputProps
|
|
45
|
+
>(function CustomTextInput(props, ref) {
|
|
41
46
|
const theme = useTheme();
|
|
42
47
|
const input = theme.inputDecorationTheme;
|
|
43
48
|
const [focused, setFocused] = useState(false);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Image } from 'expo-image';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
2
3
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
3
4
|
|
|
4
5
|
import { useTheme } from '../../core/theme';
|
|
@@ -7,21 +8,24 @@ import { Card } from '../material/card';
|
|
|
7
8
|
export class MenuActionItem {
|
|
8
9
|
readonly iconSize?: number;
|
|
9
10
|
readonly color?: string;
|
|
11
|
+
/** Élément affiché à droite de la ligne (ex. une flèche). */
|
|
12
|
+
readonly trailing?: ReactNode;
|
|
10
13
|
|
|
11
14
|
constructor(
|
|
12
15
|
readonly icon: number,
|
|
13
16
|
readonly title: string,
|
|
14
17
|
readonly subtitle: string | null | undefined,
|
|
15
18
|
readonly action: () => void,
|
|
16
|
-
options: { iconSize?: number; color?: string } = {},
|
|
19
|
+
options: { iconSize?: number; color?: string; trailing?: ReactNode } = {},
|
|
17
20
|
) {
|
|
18
21
|
this.iconSize = options.iconSize;
|
|
19
22
|
this.color = options.color;
|
|
23
|
+
this.trailing = options.trailing;
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** Liste d'actions (icône, titre, sous-titre) dans une carte, comme des `ListTile` Material. */
|
|
24
|
-
export function
|
|
28
|
+
export function MenuActionsWidget({ items }: { items: MenuActionItem[] }) {
|
|
25
29
|
const theme = useTheme();
|
|
26
30
|
return (
|
|
27
31
|
<Card>
|
|
@@ -45,6 +49,7 @@ export function MenuActionsWiget({ items }: { items: MenuActionItem[] }) {
|
|
|
45
49
|
<Text style={[theme.textTheme.headlineSmall, item.color ? { color: item.color } : null]}>{item.title}</Text>
|
|
46
50
|
{item.subtitle != null && <Text style={theme.textTheme.displaySmall}>{item.subtitle}</Text>}
|
|
47
51
|
</View>
|
|
52
|
+
{item.trailing != null && <View style={styles.trailing}>{item.trailing}</View>}
|
|
48
53
|
</Pressable>
|
|
49
54
|
))}
|
|
50
55
|
</Card>
|
|
@@ -55,4 +60,5 @@ const styles = StyleSheet.create({
|
|
|
55
60
|
tile: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 8, minHeight: 72 },
|
|
56
61
|
leading: { width: 45, height: 45, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginRight: 16 },
|
|
57
62
|
texts: { flex: 1 },
|
|
63
|
+
trailing: { marginLeft: 16 },
|
|
58
64
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '../../core/theme';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Petit loader affiché en bas d'une liste pendant le chargement de la page
|
|
7
|
+
* suivante (pagination). Sur iOS, le loader natif garde sa couleur
|
|
8
|
+
* système ; sur le web et Android, il prend la couleur primaire.
|
|
9
|
+
*/
|
|
10
|
+
export function PaginationLoaderWidget() {
|
|
11
|
+
const theme = useTheme();
|
|
12
|
+
return (
|
|
13
|
+
<View style={styles.container}>
|
|
14
|
+
<ActivityIndicator
|
|
15
|
+
size="small"
|
|
16
|
+
color={Platform.OS === 'ios' ? undefined : theme.colorScheme.primary}
|
|
17
|
+
/>
|
|
18
|
+
</View>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const styles = StyleSheet.create({
|
|
23
|
+
container: { paddingVertical: 16, alignItems: 'center', justifyContent: 'center' },
|
|
24
|
+
});
|