Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
B
beasy-mobile
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
4
Issues
4
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
G
beasy-mobile
Commits
dfdb5556
Commit
dfdb5556
authored
Sep 06, 2025
by
G
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: Transaction history screen with filters and performant scrollView using Flashlist.
parent
28d87f82
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
269 additions
and
241 deletions
+269
-241
api.ts
src/features/pay/api.ts
+5
-0
TransactionItem.tsx
src/features/pay/components/TransactionItem.tsx
+46
-0
types.ts
src/features/pay/types.ts
+16
-0
TransactionHistoryScreen.tsx
src/screens/TransactionHistoryScreen.tsx
+202
-241
No files found.
src/features/pay/api.ts
View file @
dfdb5556
...
...
@@ -5,6 +5,7 @@ import type {
OmInitializationPayload
as
OmTransactionInitializationPayload
,
OmInitializationResponse
as
OmTransactionInitializationResponse
,
PaymentType
,
Transaction
,
WaveInitializationPayload
,
WaveTransactionInitilizationResponse
,
}
from
"./types"
;
...
...
@@ -54,3 +55,7 @@ export const waveInitializeTransaction = (payload: WaveInitializationPayload) =>
export
const
waveGetTransactionStatus
=
(
id
:
string
)
=>
{
return
axiosInstance
.
get
<
WaveTransactionInitilizationResponse
>
(
`/wave-session/
${
id
}
/`
);
};
export
const
getTransactions
=
()
=>
{
return
axiosInstance
.
get
<
DjangoPaginated
<
Transaction
[]
>>
(
"/transactions/"
);
};
src/features/pay/components/TransactionItem.tsx
0 → 100644
View file @
dfdb5556
import
moment
from
"moment"
;
import
"moment/locale/fr"
;
import
{
asp
as
g
}
from
"@asp/asp"
;
import
type
{
FC
}
from
"react"
;
import
{
Text
,
View
}
from
"react-native"
;
import
type
{
Transaction
}
from
"../types"
;
import
PaymentType
from
"./PaymentType"
;
moment
.
locale
(
"fr"
);
export
const
TransactionItem
:
FC
<
{
transaction
:
Transaction
}
>
=
({
transaction
})
=>
{
const
{
type_paiement_label
,
reference
,
date
,
montant
,
status
}
=
transaction
;
const
dateObject
=
Date
.
parse
(
date
);
const
color
=
status
===
"SUCCESS"
?
"green"
:
status
===
"INITIATED"
?
"orange"
:
"red"
;
return
(
<
View
style=
{
[
g
.
w_full
,
g
.
p_sm
,
g
.
flex_row
,
g
.
gap_sm
,
g
.
rounded_sm
,
g
.
shadow_elevated
,
g
.
justify_between
,
{
backgroundColor
:
"white"
},
]
}
>
<
View
style=
{
[
g
.
flex_row
,
g
.
flex_1
,
g
.
gap_sm
]
}
>
<
View
style=
{
[
g
.
rounded_md
,
g
.
overflow_hidden
,
{
height
:
50
,
width
:
50
}]
}
>
<
PaymentType
style=
{
{
height
:
"100%"
,
aspectRatio
:
1
}
}
type=
{
type_paiement_label
}
/>
</
View
>
<
View
style=
{
[
g
.
flex_1
,
{
height
:
50
}]
}
>
<
Text
>
{
reference
}
</
Text
>
<
Text
>
{
moment
(
dateObject
).
fromNow
()
}
</
Text
>
</
View
>
</
View
>
<
View
style=
{
{
height
:
50
}
}
>
<
Text
style=
{
{
color
:
color
}
}
>
{
montant
}
</
Text
>
</
View
>
</
View
>
);
};
src/features/pay/types.ts
View file @
dfdb5556
...
...
@@ -89,3 +89,19 @@ export interface WaveTransactionInitilizationResponse {
when_created
:
string
;
when_expires
:
string
;
}
export
type
Transaction
=
{
type_paiement
:
number
;
type_paiement_label
:
PaymentTypeCode
;
marchand
:
string
;
marchand_name
:
string
;
service
:
string
;
montant
:
number
;
date
:
string
;
commentaire
:
string
;
etat
:
boolean
;
status
:
"SUCCESS"
|
"INITIATED"
|
"FAILED"
;
reference
:
string
;
transaction_id
:
number
;
marchand_code
:
string
;
};
src/screens/TransactionHistoryScreen.tsx
View file @
dfdb5556
import
{
useModalsManagerContext
}
from
"@/contexts/ModalsManagerContext"
;
import
BarWithBeasyAndNotificationsIcon
from
"@components/BarWithBeasyAndNotificationsIcon"
;
import
Button
from
"@components/Button"
;
import
Input
from
"@components/Input"
;
import
TransactionInformationsItem
from
"@components/TransactionInformationsItem"
;
import
Box
from
"@components/bases/Box"
;
import
Text
from
"@components/bases/Text"
;
import
WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight
from
"@components/wrappers/WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight"
;
import
useTransactionsHistory
,
{
type
OperatorsFilter
}
from
"@hooks/useTransactionsHistory"
;
/** biome-ignore-all lint/style/useNamingConvention: <explanation> */
import
{
asp
as
g
}
from
"@asp/asp"
;
import
{
BarnoinPayBackground
}
from
"@components/BarnoinPayBackground"
;
import
BeasyLogoIcon
from
"@components/BeasyLogoIcon"
;
import
*
as
Button
from
"@components/ButtonNew"
;
import
*
as
Input
from
"@components/InputNew"
;
import
*
as
Modal
from
"@components/Modal"
;
import
Entypo
from
"@expo/vector-icons/Entypo"
;
import
Ionicons
from
"@expo/vector-icons/Ionicons"
;
import
{
LOG
}
from
"@logger"
;
import
Card
from
"@re-card"
;
import
theme
from
"@themes/Theme"
;
import
{
useCallback
,
useState
}
from
"react"
;
import
{
RefreshControl
,
ScrollView
,
Switch
}
from
"react-native"
;
import
Icon
from
"react-native-vector-icons/Ionicons"
;
import
{
FlashList
}
from
"@shopify/flash-list"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
{
useState
}
from
"react"
;
import
{
Switch
,
Text
,
TouchableOpacity
,
View
}
from
"react-native"
;
import
{
getTransactions
}
from
"@/features/pay/api"
;
import
{
TransactionItem
}
from
"@/features/pay/components/TransactionItem"
;
import
type
{
PaymentTypeCode
}
from
"@/features/pay/types"
;
const
log
=
LOG
.
extend
(
"TransactionHistoryScreen"
);
const
TransactionHistoryScreen
=
()
=>
{
log
.
verbose
(
"TransactionHistoryScreen"
);
const
[
showFilterModal
,
setShowFilterModal
]
=
useState
(
false
);
const
[
filters
,
setFilters
]
=
useState
<
Record
<
PaymentTypeCode
,
boolean
>>
({
CB
:
true
,
FLOOZ
:
true
,
MTN
:
true
,
WAVE
:
true
,
OM
:
true
,
});
const
[
referenceFilter
,
setReferenceFilter
]
=
useState
(
""
);
const
{
transactionsHistory
:
data
,
isLoading
,
error
,
refetch
,
setReferenceFilter
,
operatorsFilter
,
setOperatorsFilter
,
}
=
useTransactionsHistory
();
const
transactionHistoryQuery
=
useQuery
({
queryKey
:
[
"transactionsHistory"
],
queryFn
:
getTransactions
,
});
const
{
showModal
}
=
useModalsManagerContext
();
const
transactions
=
transactionHistoryQuery
.
data
?.
data
.
results
?
transactionHistoryQuery
.
data
?.
data
?.
results
.
filter
(
(
transaction
)
=>
filters
[
transaction
.
type_paiement_label
]
&&
transaction
.
reference
.
includes
(
referenceFilter
),
)
:
[];
return
(
<
WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight
>
<
BarWithBeasyAndNotificationsIcon
/>
<
BarnoinPayBackground
style=
{
[
g
.
gap_5xl
]
}
>
<
View
style=
{
[
g
.
px_lg
,
g
.
flex_row
,
g
.
justify_between
]
}
>
<
BeasyLogoIcon
/>
<
Ionicons
name=
"notifications"
size=
{
24
}
color=
"black"
/>
</
View
>
<
Card
variant=
{
"curvedTopContainer"
}
padding=
{
"s"
}
height=
{
"100%"
}
mt=
{
"m"
}
>
<
Box
px=
{
"m"
}
flexDirection=
{
"row"
}
gap=
{
"s"
}
alignItems=
{
"center"
}
>
<
Box
flex=
{
1
}
>
<
Input
placeholder=
"Reference"
onChangeText=
{
setReferenceFilter
}
/>
</
Box
>
<
Box
height=
{
50
}
backgroundColor=
{
"lightGray"
}
borderRadius=
{
10
}
width=
{
50
}
// justifyContent={"center"}
alignItems=
{
"center"
}
justifyContent=
{
"center"
}
<
View
style=
{
[
g
.
px_xl
,
g
.
pt_xl
,
g
.
flex_1
,
g
.
gap_lg
,
{
backgroundColor
:
"white"
,
borderTopLeftRadius
:
20
,
borderTopRightRadius
:
20
},
]
}
>
<
View
style=
{
[
g
.
flex_row
,
g
.
gap_sm
,
g
.
align_center
,
g
.
w_full
]
}
>
<
Input
.
Container
style=
{
[
g
.
flex_1
]
}
>
<
Input
.
FieldContainer
>
<
Input
.
Field
placeholder=
"Recherche par référence"
placeholderTextColor=
{
"gray"
}
onChangeText=
{
setReferenceFilter
}
/>
</
Input
.
FieldContainer
>
</
Input
.
Container
>
<
TouchableOpacity
onPress=
{
()
=>
setShowFilterModal
(
true
)
}
style=
{
[
g
.
justify_center
,
g
.
align_center
,
g
.
p_md
,
g
.
rounded_md
,
{
backgroundColor
:
"#e8e8e9ff"
},
]
}
>
<
Icon
name=
"filter"
size=
{
30
}
color=
"black"
// onPress={() => setShowFiltersModal(true)}
onPress=
{
()
=>
showModal
(
<
FiltersModal
setOperatorsFilter=
{
setOperatorsFilter
}
operatorsFilter=
{
operatorsFilter
}
/>,
)
}
/>
</
Box
>
</
Box
>
<
ScrollView
// style={{ backgroundColor: "red" }}
refreshControl=
{
<
RefreshControl
refreshing=
{
isLoading
}
onRefresh=
{
refetch
}
/>
}
contentContainerStyle=
{
{
gap
:
10
,
padding
:
5
,
// marginTop: 10,
paddingBottom
:
30
,
flexDirection
:
"column"
,
}
}
showsVerticalScrollIndicator=
{
false
}
>
{
data
?.
map
((
transaction
)
=>
(
<
TransactionInformationsItem
key=
{
transaction
.
transaction_id
}
paymentType=
{
transaction
.
type_paiement_label
}
reference=
{
transaction
.
reference
}
amount=
{
transaction
.
montant
}
date=
{
transaction
.
date
}
status=
{
transaction
.
status
}
/>
))
}
</
ScrollView
>
</
Card
>
</
WrapperWithDefaultBeasyBackgroundAndSafeAreaTopLeftRight
>
);
};
<
Ionicons
name=
"filter"
size=
{
24
}
color=
"black"
/>
</
TouchableOpacity
>
</
View
>
export
default
TransactionHistoryScreen
;
interface
FiltersModalProps
{
// biome-ignore lint/style/useNamingConvention: <explanation>
operatorsFilter
:
OperatorsFilter
;
// biome-ignore lint/style/useNamingConvention: <explanation>
setOperatorsFilter
:
React
.
Dispatch
<
React
.
SetStateAction
<
OperatorsFilter
>>
;
}
const
FiltersModal
:
React
.
FC
<
FiltersModalProps
>
=
({
operatorsFilter
,
setOperatorsFilter
})
=>
{
const
[
filterOm
,
setFilterOm
]
=
useState
(
operatorsFilter
.
OM
);
const
[
filterMtn
,
setFilterMtn
]
=
useState
(
operatorsFilter
.
MTN
);
const
[
filterFlooz
,
setFilterFlooz
]
=
useState
(
operatorsFilter
.
FLOOZ
);
const
[
filterWave
,
setFilterWave
]
=
useState
(
operatorsFilter
.
WAVE
);
const
[
filterCb
,
setFilterCb
]
=
useState
(
operatorsFilter
.
CB
);
const
saveOperatorsFilters
=
useCallback
(()
=>
{
setOperatorsFilter
({
// biome-ignore lint/style/useNamingConvention: <PaymentCode Type requirement>
OM
:
filterOm
,
// biome-ignore lint/style/useNamingConvention: <PaymentCode Type requirement>
MTN
:
filterMtn
,
// biome-ignore lint/style/useNamingConvention: <PaymentCode Type requirement>
FLOOZ
:
filterFlooz
,
// biome-ignore lint/style/useNamingConvention: <PaymentCode Type requirement>
WAVE
:
filterWave
,
// biome-ignore lint/style/useNamingConvention: <PaymentCode Type requirement>
CB
:
filterCb
,
});
},
[
filterOm
,
filterMtn
,
filterFlooz
,
filterWave
,
filterCb
,
setOperatorsFilter
]);
const
{
closeModal
}
=
useModalsManagerContext
();
{
!
transactions
?
(
<
Text
style=
{
[
g
.
text_center
]
}
>
Aucune transaction n'a été trouvée
</
Text
>
)
:
(
<
FlashList
data=
{
transactions
}
keyExtractor=
{
(
item
)
=>
item
.
reference
}
estimatedItemSize=
{
75
}
bounces=
{
false
}
renderItem=
{
({
item
})
=>
(
<
View
style=
{
[
g
.
p_sm
]
}
key=
{
item
.
reference
}
>
<
TransactionItem
transaction=
{
item
}
/>
</
View
>
)
}
/>
)
}
</
View
>
const
saveFilters
=
useCallback
(()
=>
{
saveOperatorsFilters
();
closeModal
();
},
[
saveOperatorsFilters
,
closeModal
]);
{
/* FILTER MODAL */
}
return
(
<
Card
variant=
{
"absoluteForegroundScreenSizedTransparentCard"
}
>
<
Box
backgroundColor=
{
"white"
}
width=
{
"90%"
}
// height={"70%"}
maxHeight=
{
"80%"
}
borderRadius=
{
10
}
p=
{
"m"
}
flexDirection=
{
"column"
}
style=
{
{
margin
:
"auto"
}
}
<
Modal
.
RnModal
visible=
{
showFilterModal
}
style=
{
{
backgroundColor
:
"rgba(0, 0, 0, 0.25)"
,
flex
:
1
}
}
>
<
Box
justifyContent=
{
"space-between"
}
alignItems=
{
"center"
}
flexDirection=
{
"row"
}
width=
{
"100%"
}
<
Modal
.
OuterView
style=
{
[
g
.
p_md
,
g
.
shadow_elevated
,
{
backgroundColor
:
"white"
,
width
:
"90%"
,
height
:
"60%"
,
},
]
}
>
<
Text
fontSize=
{
20
}
variant=
{
"black"
}
fontWeight=
{
"bold"
}
>
Paramétrage filtre
</
Text
>
<
Icon
name=
"close-outline"
size=
{
30
}
color=
"black"
onPress=
{
()
=>
{
closeModal
();
}
}
/>
</
Box
>
<
Text
fontSize=
{
20
}
fontWeight=
{
"bold"
}
my=
{
"s"
}
>
Opérateurs
</
Text
>
<
Box
width=
{
"100%"
}
backgroundColor=
{
"lightGray"
}
borderRadius=
{
10
}
p=
{
"s"
}
gap=
{
"s"
}
>
<
Box
flexDirection=
{
"row"
}
alignItems=
{
"center"
}
justifyContent=
{
"space-between"
}
borderBottomColor=
{
"white"
}
borderBottomWidth=
{
1
}
py=
{
"s"
}
>
<
Text
fontWeight=
{
"bold"
}
>
Orange Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
theme
.
colors
.
secondary
}
}
value=
{
filterOm
}
onValueChange=
{
setFilterOm
}
/>
</
Box
>
<
Box
flexDirection=
{
"row"
}
alignItems=
{
"center"
}
justifyContent=
{
"space-between"
}
borderBottomColor=
{
"white"
}
borderBottomWidth=
{
1
}
py=
{
"s"
}
>
<
Text
fontWeight=
{
"bold"
}
>
MTN Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
theme
.
colors
.
secondary
}
}
value=
{
filterMtn
}
onValueChange=
{
setFilterMtn
}
/>
</
Box
>
<
Box
flexDirection=
{
"row"
}
alignItems=
{
"center"
}
justifyContent=
{
"space-between"
}
borderBottomColor=
{
"white"
}
borderBottomWidth=
{
1
}
py=
{
"s"
}
>
<
Text
fontWeight=
{
"bold"
}
>
Flooz Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
theme
.
colors
.
secondary
}
}
value=
{
filterFlooz
}
onValueChange=
{
setFilterFlooz
}
/>
</
Box
>
<
Box
flexDirection=
{
"row"
}
alignItems=
{
"center"
}
justifyContent=
{
"space-between"
}
borderBottomColor=
{
"white"
}
borderBottomWidth=
{
1
}
py=
{
"s"
}
>
<
Text
fontWeight=
{
"bold"
}
>
Wave
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
theme
.
colors
.
secondary
}
}
value=
{
filterWave
}
onValueChange=
{
setFilterWave
}
/>
</
Box
>
<
Box
flexDirection=
{
"row"
}
alignItems=
{
"center"
}
justifyContent=
{
"space-between"
}
borderBottomColor=
{
"white"
}
py=
{
"s"
}
>
<
Text
fontWeight=
{
"bold"
}
>
Carte Bancaire
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
theme
.
colors
.
secondary
}
}
value=
{
filterCb
}
onValueChange=
{
setFilterCb
}
/>
</
Box
>
</
Box
>
<
Box
style=
{
{
marginTop
:
20
}
}
>
<
Button
variant=
{
"full"
}
label=
{
"Valider"
}
textVariants=
{
"white"
}
onPress=
{
saveFilters
}
/>
</
Box
>
</
Box
>
</
Card
>
<
View
style=
{
[
g
.
p_md
,
g
.
flex_1
,
g
.
gap_sm
]
}
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
]
}
>
<
Text
style=
{
[
g
.
font_bold
,
g
.
text_2xl
]
}
>
Paramétrage du filtre
</
Text
>
<
Entypo
name=
"cross"
size=
{
24
}
color=
"black"
/>
</
View
>
<
Text
style=
{
[
g
.
font_bold
,
g
.
text_lg
]
}
>
Opétareurs
</
Text
>
<
View
style=
{
[
g
.
px_md
,
{
backgroundColor
:
"#e8e8e9ff"
}]
}
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
,
g
.
py_md
,
{
borderBottomColor
:
"white"
,
borderBottomWidth
:
1
},
]
}
>
<
Text
style=
{
[
g
.
font_bold
]
}
>
Orange Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
"#167a00ff"
}
}
value=
{
filters
.
OM
}
onValueChange=
{
(
checked
)
=>
setFilters
((
prev
)
=>
({
...
prev
,
OM
:
checked
}))
}
/>
</
View
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
,
g
.
py_md
,
{
borderBottomColor
:
"white"
,
borderBottomWidth
:
1
},
]
}
>
<
Text
style=
{
[
g
.
font_bold
]
}
>
MTN Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
"#167a00ff"
}
}
value=
{
filters
.
MTN
}
onValueChange=
{
(
checked
)
=>
setFilters
((
prev
)
=>
({
...
prev
,
MTN
:
checked
}))
}
/>
</
View
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
,
g
.
py_md
,
{
borderBottomColor
:
"white"
,
borderBottomWidth
:
1
},
]
}
>
<
Text
style=
{
[
g
.
font_bold
]
}
>
Flooz Money
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
"#167a00ff"
}
}
value=
{
filters
.
FLOOZ
}
onValueChange=
{
(
checked
)
=>
setFilters
((
prev
)
=>
({
...
prev
,
FLOOZ
:
checked
}))
}
/>
</
View
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
,
g
.
py_md
,
{
borderBottomColor
:
"white"
,
borderBottomWidth
:
1
},
]
}
>
<
Text
style=
{
[
g
.
font_bold
]
}
>
Wave
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
"#167a00ff"
}
}
value=
{
filters
.
WAVE
}
onValueChange=
{
(
checked
)
=>
setFilters
((
prev
)
=>
({
...
prev
,
WAVE
:
checked
}))
}
/>
</
View
>
<
View
style=
{
[
g
.
flex_row
,
g
.
justify_between
,
g
.
align_center
,
g
.
py_md
]
}
>
<
Text
style=
{
[
g
.
font_bold
]
}
>
Carte Bancaire
</
Text
>
<
Switch
trackColor=
{
{
false
:
"#767577"
,
true
:
"#167a00ff"
}
}
value=
{
filters
.
CB
}
onValueChange=
{
(
checked
)
=>
setFilters
((
prev
)
=>
({
...
prev
,
CB
:
checked
}))
}
/>
</
View
>
</
View
>
<
Button
.
Container
onPress=
{
()
=>
setShowFilterModal
(
false
)
}
>
<
Button
.
Label
>
Valider
</
Button
.
Label
>
</
Button
.
Container
>
</
View
>
</
Modal
.
OuterView
>
</
Modal
.
RnModal
>
</
BarnoinPayBackground
>
);
};
export
default
TransactionHistoryScreen
;
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment