From c6ba774167d3a5f156d6a296b22590fe44e7640a Mon Sep 17 00:00:00 2001 From: Abin Toppo Date: Fri, 27 Mar 2026 13:16:17 +0530 Subject: [PATCH] update --- src/navigation/BottomTabs.tsx | 12 +- src/screens/home/Home.tsx | 2 +- src/screens/management/ManagementScreen.tsx | 103 ++++++- src/screens/transaction/TransactionList.tsx | 287 ++++++++++++++------ 4 files changed, 302 insertions(+), 102 deletions(-) diff --git a/src/navigation/BottomTabs.tsx b/src/navigation/BottomTabs.tsx index 288cb3c..180b448 100644 --- a/src/navigation/BottomTabs.tsx +++ b/src/navigation/BottomTabs.tsx @@ -42,19 +42,19 @@ const BottomTabs: React.FC = () => { tabBarStyle: { position: "absolute", bottom: Math.max(insets.bottom, 8), - left: 12, - right: 12, + left: 40, + right: 40, height: 64, - borderRadius: 18, + borderRadius: 22, backgroundColor: theme.colors.surface, borderWidth: 1, borderColor: theme.colors.border, borderTopWidth: 0, - elevation: 8, + elevation: 10, shadowColor: "#000", shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.12, - shadowRadius: 8, + shadowOpacity: 0.15, + shadowRadius: 10, paddingBottom: 0, }, tabBarItemStyle: { diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index dffd64c..eb37451 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -38,7 +38,7 @@ const organizerActions: ActionItem[] = [ { id: '2', title: 'Joined Groups', subtitle: 'Circles you belong to', action: 'NAVIGATE_MANAGEMENT', tab: 'Joined Groups', iconName: 'people-outline' }, { id: '3', title: 'Requests', subtitle: 'Pending join requests', action: 'NAVIGATE_MANAGEMENT', tab: 'Requests', iconName: 'notifications-outline' }, { id: '4', title: 'Create Group', subtitle: 'Start a new circle', action: 'NAVIGATE_CREATE_GROUP', iconName: 'add-circle-outline' }, - { id: '5', title: 'Your Requests', subtitle: 'Status of your applications', action: 'NAVIGATE_MANAGEMENT', tab: 'Your Requests', iconName: 'mail-open-outline' }, + { id: '5', title: 'Your Requests', subtitle: 'Status of your applications', action: 'NAVIGATE_MANAGEMENT', tab: 'Requests', iconName: 'mail-open-outline' }, { id: '6', title: 'Transactions', subtitle: 'View payment history', action: 'TRANSACTION', iconName: 'receipt-outline' }, ]; diff --git a/src/screens/management/ManagementScreen.tsx b/src/screens/management/ManagementScreen.tsx index 86c0950..908ae90 100644 --- a/src/screens/management/ManagementScreen.tsx +++ b/src/screens/management/ManagementScreen.tsx @@ -35,7 +35,8 @@ import FontsFamily from '../../constents/FontsFamily'; import { RootTabParamList } from '../../navigation/BottomTabs'; type ManagementRouteProp = RouteProp; -type ActiveTab = 'My Groups' | 'Joined Groups' | 'Requests' | 'Your Requests'; +type ActiveTab = 'My Groups' | 'Joined Groups' | 'Requests' | 'My Requests'; +type RequestSubTab = 'Join Requests' | 'My Requests'; type GroupStatusFilter = 'ALL' | 'PENDING' | 'APPROVED' | 'ACTIVE' | 'DELAY' | 'COMPLETED' | 'REJECTED' | 'CANCELLED'; const GROUP_STATUS_FILTERS: GroupStatusFilter[] = [ @@ -74,7 +75,8 @@ const ManagementScreen: React.FC = () => { user.role === 'ORGANIZER' ? 'My Groups' : 'Joined Groups' ); const [myGroupStatusFilter, setMyGroupStatusFilter] = useState('APPROVED'); - const [joinedGroupStatusFilter, setJoinedGroupStatusFilter] = useState('ACTIVE'); + const [joinedGroupStatusFilter, setJoinedGroupStatusFilter] = useState('ALL'); + const [requestSubTab, setRequestSubTab] = useState('Join Requests'); useEffect(() => { if (!route.params?.actionTab) return; @@ -102,6 +104,9 @@ const ManagementScreen: React.FC = () => { }; }, [joinedGroupStatusFilter]); + const requestsParams = useMemo(() => ({ page: 0, size: 10 }), []); + const myRequestsParams = useMemo(() => ({ page: 0, size: 10 }), []); + console.log('joinedGroupsParams:', joinedGroupsParams); @@ -117,13 +122,12 @@ const ManagementScreen: React.FC = () => { ); const { data: requestsRes, isLoading: requestsLoading, refetch: refetchRequests } = useFetch( REQUESTS_URL, - undefined, + requestsParams, { enabled: user.role === 'ORGANIZER' } ); const { data: userReqRes, isLoading: userReqLoading, refetch: refetchUserReq } = useFetch( USER_REQUEST, - undefined, - { enabled: user.role !== 'ORGANIZER' } + myRequestsParams ); const { mutate: updateRequest } = useUpdate(''); @@ -155,7 +159,7 @@ const ManagementScreen: React.FC = () => { const tabs: ActiveTab[] = useMemo(() => user.role === 'ORGANIZER' ? ['My Groups', 'Joined Groups', 'Requests'] - : ['Joined Groups', 'Your Requests'], + : ['Joined Groups', 'My Requests'], [user.role]); const handleApprove = (req: JoinRequest) => { @@ -192,8 +196,9 @@ const ManagementScreen: React.FC = () => { switch (activeTab) { case 'My Groups': return myGroups; case 'Joined Groups': return joinedGroups; - case 'Requests': return incomingRequests; - case 'Your Requests': return myRequests; + case 'Requests': + return requestSubTab === 'Join Requests' ? incomingRequests : myRequests; + case 'My Requests': return myRequests; default: return []; } }; @@ -203,8 +208,24 @@ const ManagementScreen: React.FC = () => { return ( - Account Hub - Manage your circles and requests + + + Account Hub + Manage your circles and requests + + + + + @@ -306,6 +327,47 @@ const ManagementScreen: React.FC = () => { )} + + + {activeTab === 'Requests' && user.role === 'ORGANIZER' && ( + + + {(['Join Requests', 'My Requests'] as RequestSubTab[]).map((tab, index) => { + const isActive = requestSubTab === tab; + return ( + setRequestSubTab(tab)} + style={[ + styles.statusFilterChip, + index !== (['Join Requests', 'My Requests'] as RequestSubTab[]).length - 1 && styles.statusFilterChipSpaced, + { + borderColor: isActive ? theme.colors.primary : theme.colors.border, + backgroundColor: isActive ? theme.colors.primarySoft : theme.colors.surface, + }, + ]} + > + + {tab} + + + ); + })} + + + )} + { renderItem={({ item }) => { if (activeTab === 'My Groups') return ; if (activeTab === 'Joined Groups') return ; - if (activeTab === 'Requests') return ; - if (activeTab === 'Your Requests') return ; + if (activeTab === 'Requests') { + if (requestSubTab === 'Join Requests') { + return ; + } else { + return ; + } + } + if (activeTab === 'My Requests') return ; return null; }} refreshControl={ @@ -344,6 +412,17 @@ const styles = StyleSheet.create({ paddingBottom: 12, zIndex: 10, }, + headerTitleRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + refreshBtn: { + padding: 8, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + }, title: { fontSize: responsiveFontSize(3.2), fontFamily: FontsFamily.nunitoBold, diff --git a/src/screens/transaction/TransactionList.tsx b/src/screens/transaction/TransactionList.tsx index f229093..1a44f42 100644 --- a/src/screens/transaction/TransactionList.tsx +++ b/src/screens/transaction/TransactionList.tsx @@ -13,93 +13,139 @@ import { TRANSACTION } from "../../utils/ApiUrls"; import colors from "../../components/colors"; import AppView from "../../components/AppView"; import Header from "../../components/common/Header"; +import LucideIcon from "../../components/common/LucideIcon"; +import { useTheme } from "../../hooks/ui/useTheme"; +import FontsFamily from "../../constents/FontsFamily"; +import { responsiveFontSize } from "react-native-responsive-dimensions"; type Transaction = { - id: string; - fundGroupId: string; + txnId: string; + fundGroupId: number; fundGroupName: string; amount: number; status: string; - payerMobile?: string; - transactionDate: number[]; + payerMobile: string; + transactionDate: string; + currency: string; + orderNo: string; }; type ApiResponse = { message: string; status: string; statusCode: number; - data: Transaction[]; + data: { + content: Transaction[]; + }; }; -// Helper: convert the array-style date into a JS Date -const parseTransactionDate = (arr: number[] | undefined): Date | null => { - if (!arr || arr.length < 6) return null; - const [year, month, day, hour = 0, minute = 0, second = 0, sub = 0] = arr; - let milli = 0; - if (sub > 999999999) { - milli = Math.floor(sub / 1e6); - } else if (sub > 1000) { - milli = Math.floor(sub / 1e6); - } else { - milli = sub; - } - return new Date(year, month - 1, day, hour, minute, second, milli); -}; - -const formatINR = (amount: number) => { +const formatCurrency = (amount: number, currency: string = "UGX") => { try { - return new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(amount); + return new Intl.NumberFormat("en-UG", { + style: "currency", + currency: currency || "UGX", + minimumFractionDigits: 0, + }).format(amount); } catch (e) { - return `UGX${amount.toFixed(2)}`; + return `${currency || "UGX"} ${amount.toLocaleString()}`; } }; const StatusBadge: React.FC<{ status?: string }> = ({ status }) => { + const theme = useTheme(); const text = status ?? "FAILED"; - const color = - text === "SUCCESS" ? styles.statusSuccess : text === "PENDING" ? styles.statusPending : styles.statusFailed; + + const getStatusColors = (s: string) => { + switch (s) { + case 'SUCCESS': + return { bg: theme.colors.success + '15', text: theme.colors.success }; + case 'SENT_TO_PROCESSOR': + case 'PENDING': + return { bg: theme.colors.warning + '15', text: theme.colors.warning }; + default: + return { bg: theme.colors.error + '15', text: theme.colors.error }; + } + }; + + const colors = getStatusColors(text); return ( - - {text} + + {text} ); }; const TransactionItem: React.FC<{ item: Transaction }> = ({ item }) => { + const theme = useTheme(); + const formatDate = (dateStr: string | null): string => { if (!dateStr) return 'N/A'; - - const date = new Date(dateStr); - const day = String(date.getDate()).padStart(2, '0'); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const year = date.getFullYear(); - - return `${day}-${month}-${year}`; + try { + const date = new Date(dateStr); + const day = String(date.getDate()).padStart(2, '0'); + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + return `${day} ${months[date.getMonth()]} ${date.getFullYear()}`; + } catch (e) { + return 'N/A'; + } }; - const dateStr = item.transactionDate ? formatDate(item.transactionDate) : "N/A"; + const isSuccess = item.status === 'SUCCESS'; - console.log("item of the transaction : ", item) return ( - - - {item.fundGroupName} - Payer: {item.payerMobile ?? "-"} - {dateStr} + + + + {item.fundGroupName} + + by {item.payerMobile ?? "-"} + + + - - {formatINR(item.amount)} - + + + Amount + + {formatCurrency(item.amount, item.currency)} + + + + Order No + + {item.orderNo} + + + + + + + {formatDate(item.transactionDate)} + + + {isSuccess ? ( + + ✓ SUCCESS + + ) : ( + + CONTRIBUTION + + )} ); }; const TransactionsList: React.FC = () => { + const theme = useTheme(); const [refreshing, setRefreshing] = useState(false); const [transactions, setTransactions] = useState([]); const [message, setMessage] = useState(""); @@ -109,7 +155,7 @@ const TransactionsList: React.FC = () => { const res = (await apiCall(TRANSACTION, "GET")) as ApiResponse; console.log("transaction list response : ", res); if (res.statusCode === 200) { - setTransactions(res.data); + setTransactions(res.data?.content || []); setMessage(res.message); } } catch (error) { @@ -134,18 +180,23 @@ const TransactionsList: React.FC = () => { const renderEmpty = () => ( - No transactions - Transactions will appear here when available. + + No transactions + Transactions will appear here when available. ); + const isLoading = false; // Add placeholder or hook if needed + return ( - -
- {message} + +
+ + {message} + item.id} + keyExtractor={(item) => item.txnId || item.orderNo} renderItem={({ item }) => } ListEmptyComponent={renderEmpty} contentContainerStyle={transactions.length === 0 ? { flex: 1 } : undefined} @@ -158,44 +209,114 @@ const TransactionsList: React.FC = () => { export default TransactionsList; const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#f7f7fb" }, - headerTitle: { fontSize: 18, fontWeight: "500", color: colors.primary }, - headerSubtitle: { marginTop: 4, color: "#6b7280" }, - + headerTitle: { + fontSize: 18, + fontFamily: FontsFamily.nunitoSemiBold, + color: colors.primary + }, card: { - flexDirection: "row", - justifyContent: "space-between", - padding: 16, - // marginHorizontal: 12, - marginVertical: 8, - backgroundColor: "#fff", - borderRadius: 12, - shadowColor: "#000", - shadowOpacity: 0.03, + borderRadius: 16, + padding: 14, + marginBottom: 12, + marginHorizontal: 14, + borderWidth: 1, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.05, shadowRadius: 8, elevation: 2, }, - - cardLeft: { flex: 1 }, - fundName: { fontSize: 16, fontWeight: "600" }, - payer: { marginTop: 6, color: "#6b7280" }, - date: { marginTop: 6, color: "#9ca3af" }, - - cardRight: { alignItems: "flex-end", justifyContent: "space-between" }, - amount: { fontSize: 16, fontWeight: "700" }, - + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: 10, + }, + titleContainer: { + flex: 1, + }, + fundName: { + fontSize: responsiveFontSize(2), + fontFamily: FontsFamily.nunitoExtraBold, + marginBottom: 2, + }, + payer: { + fontSize: responsiveFontSize(1.4), + fontFamily: FontsFamily.nunitoRegular, + }, statusBadge: { - marginTop: 8, paddingHorizontal: 8, paddingVertical: 4, - borderRadius: 999, + borderRadius: 6, + }, + statusText: { + fontSize: responsiveFontSize(1.2), + fontFamily: FontsFamily.nunitoBold, + }, + infoRow: { + flexDirection: 'row', + paddingVertical: 10, + borderTopWidth: 1, + borderBottomWidth: 1, + marginBottom: 10, + }, + infoItem: { + flex: 1, + }, + infoLabel: { + fontSize: responsiveFontSize(1.3), + fontFamily: FontsFamily.nunitoRegular, + marginBottom: 2, + }, + infoValue: { + fontSize: responsiveFontSize(1.6), + fontFamily: FontsFamily.nunitoBold, + }, + cardFooter: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: 4, + }, + dateText: { + fontSize: responsiveFontSize(1.3), + fontFamily: FontsFamily.nunitoMedium, + }, + typeBadge: { + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 6, + }, + typeText: { + fontSize: responsiveFontSize(1.15), + fontFamily: FontsFamily.nunitoBold, + letterSpacing: 0.5, + }, + successBadge: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 6, + }, + successText: { + fontSize: responsiveFontSize(1.3), + fontFamily: FontsFamily.nunitoBold, + letterSpacing: 0.5, + }, + emptyContainer: { + alignItems: "center", + justifyContent: "center", + marginTop: 100 + }, + emptyTitle: { + fontSize: responsiveFontSize(2), + fontFamily: FontsFamily.nunitoBold, + marginTop: 16 + }, + emptySubtitle: { + marginTop: 8, + fontSize: responsiveFontSize(1.6), + fontFamily: FontsFamily.nunitoRegular, + textAlign: 'center', + paddingHorizontal: 40 }, - statusText: { fontSize: 12, fontWeight: "600", color: "#fff" }, - statusSuccess: { backgroundColor: "#10b981" }, - statusPending: { backgroundColor: "#f59e0b" }, - statusFailed: { backgroundColor: "#ef4444" }, - - emptyContainer: { alignItems: "center", justifyContent: "center", }, - emptyTitle: { fontSize: 18, fontWeight: "600", color: "#374151" }, - emptySubtitle: { marginTop: 8, color: "#6b7280" }, });