work done

This commit is contained in:
Abin Toppo
2025-10-01 18:52:51 +05:30
parent 017c470fd3
commit a4fb2fe269
4 changed files with 611 additions and 193 deletions
+1
View File
@@ -46,6 +46,7 @@ const styles = StyleSheet.create({
alignItems: "center",
justifyContent: "space-between",
paddingVertical: 12,
paddingHorizontal:12
},
headerTitle: {
fontSize: 18,
@@ -0,0 +1,317 @@
import React, { useState, useCallback } from 'react';
import {
StyleSheet,
Text,
View,
FlatList,
TouchableOpacity,
LayoutAnimation,
UIManager,
Platform,
} from 'react-native';
import { Ionicons } from "@react-native-vector-icons/ionicons";import colors from "../../../components/colors"; // Assuming a colors file
// Enable LayoutAnimation for Android
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
// --- TypeScript Interfaces for Type Safety ---
interface Contribution {
transactionId: string;
userName: string;
baseAmount: number;
lateFee: number;
waivedAmount: number;
totalAmount: number;
status: 'SUCCESS' | 'PENDING' | 'FAILED';
paidDate: [number, number, number];
}
interface Withdrawal {
transactionId: string;
userName: string;
baseAmount: number;
totalAmount: number;
status: 'SUCCESS' | 'PENDING' | 'FAILED';
paidDate: [number, number, number];
}
interface Cycle {
cycleNumber: number;
cycleStart: [number, number, number];
cycleEnd: [number, number, number];
expectedContribution: number;
contributions: Contribution[];
withdrawal: Withdrawal | null;
totalParticipants: number;
totalCollected: number;
collectionPercentage: number;
paidParticipants: number;
pendingParticipants: number;
}
interface CycleDashboardData {
groupId: string;
groupName: string;
totalCycles: number;
currentCycle: number;
contributionAmount: number;
cycleType: 'MONTHLY' | 'WEEKLY' | 'DAILY';
status: 'COMPLETED' | 'ACTIVE' | 'PENDING';
cycles: Cycle[];
totalParticipants: number;
totalCollected: number;
}
// --- Helper Functions ---
const formatDate = (dateArray: [number, number, number]): string => {
if (!dateArray || dateArray.length < 3) return 'N/A';
const [year, month, day] = dateArray;
return new Date(year, month - 1, day).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
});
};
const formatCurrency = (amount: number): string => {
return new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR',
minimumFractionDigits: 0,
}).format(amount);
};
// --- Child Components ---
const CycleItem = ({ item, isExpanded, onToggle }: { item: Cycle, isExpanded: boolean, onToggle: () => void }) => {
return (
<View style={styles.cycleCard}>
<TouchableOpacity onPress={onToggle} style={styles.cycleHeader}>
<View>
<Text style={styles.cycleTitle}>Cycle {item.cycleNumber}</Text>
<Text style={styles.cycleDateRange}>
{formatDate(item.cycleStart)} - {formatDate(item.cycleEnd)}
</Text>
</View>
<Ionicons name={isExpanded ? 'chevron-up' : 'chevron-down'} size={24} color={colors.black} />
</TouchableOpacity>
{isExpanded && (
<View style={styles.cycleDetails}>
{/* Withdrawal Info */}
{item.withdrawal && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Withdrawal</Text>
<View style={styles.withdrawalCard}>
<Ionicons name="arrow-down-circle-outline" size={32} color={colors.Home} />
<View style={styles.withdrawalInfo}>
<Text style={styles.withdrawalUser}>{item.withdrawal.userName}</Text>
<Text style={styles.withdrawalAmount}>{formatCurrency(item.withdrawal.totalAmount)}</Text>
</View>
<Text style={styles.withdrawalDate}>{formatDate(item.withdrawal.paidDate)}</Text>
</View>
</View>
)}
{/* Contributions List */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Contributions ({item.paidParticipants}/{item.totalParticipants})</Text>
{item.contributions.map(contribution => (
<View key={contribution.transactionId} style={styles.contributionItem}>
<Ionicons name="person-circle-outline" size={28} color="#475569" />
<View style={{ flex: 1, marginLeft: 10 }}>
<Text style={styles.contributorName}>{contribution.userName}</Text>
<Text style={styles.contributionDate}>{formatDate(contribution.paidDate)}</Text>
</View>
<Text style={styles.contributionAmount}>{formatCurrency(contribution.totalAmount)}</Text>
</View>
))}
</View>
</View>
)}
</View>
);
};
// --- Main Dashboard Component ---
const CycleDashboard = ({ data }: { data: CycleDashboardData }) => {
const [expandedCycle, setExpandedCycle] = useState<number | null>(data.currentCycle);
const handleToggle = (cycleNumber: number) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setExpandedCycle(expandedCycle === cycleNumber ? null : cycleNumber);
};
const renderHeader = () => (
<View style={styles.headerContainer}>
<Text style={styles.groupName}>{data.groupName}</Text>
<View style={styles.summaryRow}>
<View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Total Collected</Text>
<Text style={styles.summaryValue}>{formatCurrency(data.totalCollected)}</Text>
</View>
<View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Status</Text>
<Text style={[styles.summaryValue, { color: data.status === 'COMPLETED' ? colors.Settings : colors.Home }]}>{data.status}</Text>
</View>
<View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Cycles</Text>
<Text style={styles.summaryValue}>{data.currentCycle}/{data.totalCycles}</Text>
</View>
</View>
</View>
);
return (
<FlatList
data={data.cycles}
keyExtractor={(item) => item.cycleNumber.toString()}
renderItem={({ item }) => (
<CycleItem
item={item}
isExpanded={expandedCycle === item.cycleNumber}
onToggle={() => handleToggle(item.cycleNumber)}
/>
)}
ListHeaderComponent={renderHeader}
contentContainerStyle={styles.container}
/>
);
};
export default CycleDashboard;
// --- Styles ---
const styles = StyleSheet.create({
container: {
paddingHorizontal: 15,
paddingBottom: 20,
},
// Header
headerContainer: {
marginBottom: 20,
},
groupName: {
fontSize: 24,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 15,
},
summaryRow: {
flexDirection: 'row',
justifyContent: 'space-between',
},
summaryBox: {
alignItems: 'center',
},
summaryLabel: {
fontSize: 14,
color: '#64748b',
marginBottom: 4,
},
summaryValue: {
fontSize: 16,
fontWeight: '600',
color: '#0f172a',
},
// Cycle Item Card
cycleCard: {
backgroundColor: '#fff',
borderRadius: 12,
marginBottom: 15,
elevation: 2,
shadowColor: '#000',
shadowOpacity: 0.08,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
overflow: 'hidden',
},
cycleHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
},
cycleTitle: {
fontSize: 18,
fontWeight: '600',
color: '#1e293b',
},
cycleDateRange: {
fontSize: 13,
color: '#64748b',
marginTop: 2,
},
cycleDetails: {
paddingHorizontal: 16,
paddingBottom: 16,
borderTopWidth: 1,
borderTopColor: '#f1f5f9',
},
section: {
marginTop: 16,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#334155',
marginBottom: 12,
},
// Withdrawal Card
withdrawalCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.pastelPink, // Example color
padding: 12,
borderRadius: 8,
},
withdrawalInfo: {
flex: 1,
marginLeft: 12,
},
withdrawalUser: {
fontSize: 15,
fontWeight: 'bold',
color: colors.Home
},
withdrawalAmount: {
fontSize: 14,
color: colors.Home
},
withdrawalDate: {
fontSize: 12,
color: '#475569'
},
// Contribution Item
contributionItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#f1f5f9',
},
contributorName: {
fontSize: 15,
color: '#1e293b',
},
contributionDate: {
fontSize: 12,
color: '#64748b',
},
contributionAmount: {
fontSize: 15,
fontWeight: '500',
color: '#1e293b',
},
});
+291 -192
View File
@@ -1,15 +1,15 @@
import React, { useRef, useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
FlatList,
Animated,
ToastAndroid,
Alert,
RefreshControl,
ScrollView,
Modal,
} from "react-native";
import { useRoute, RouteProp } from "@react-navigation/native";
import { useRoute } from "@react-navigation/native";
import AppView from "../../../components/AppView";
import colors from "../../../components/colors";
import { Ionicons } from "@react-native-vector-icons/ionicons";
@@ -17,30 +17,62 @@ import Header from "../../../components/common/Header";
import { apiCall } from "../../../utils/ApiHelper";
import { BASE_URL, CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls";
import ContributionScreen from "../../ContributionScreen";
import CycleDashboard from "./CycleDashboard";
// Define type for a member
interface Member {
id: string;
name: string;
role: "Organizer" | "Member";
contribution: number;
data: any
}
const CustomDropdown = ({ items, selectedValue, onValueChange, placeholder = "Select an item" }:any) => {
const [open, setOpen] = useState(false);
const selectedLabel = items.find((item:any) => item.value === selectedValue)?.label || placeholder;
// Define route params type
return (
<View style={styles.dropdownWrapper}>
<TouchableOpacity onPress={() => setOpen(true)} style={styles.dropdownHeader}>
<Text style={styles.headerText}>{selectedLabel}</Text>
<Ionicons name='chevron-down' size={22} color={colors.black} />
</TouchableOpacity>
<Modal
transparent
animationType="fade"
visible={open}
onRequestClose={() => setOpen(false)}
>
<TouchableOpacity style={styles.modalOverlay} onPress={() => setOpen(false)}>
<View style={styles.dropdownListContainer}>
<ScrollView>
{items.map(item => (
<TouchableOpacity
key={item.value}
onPress={() => {
onValueChange(item.value);
setOpen(false);
}}
style={[
styles.dropdownItem,
item.value === selectedValue && styles.selectedItem
]}
>
<Text style={styles.itemText}>{item.label}</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
</TouchableOpacity>
</Modal>
</View>
);
};
const GroupDetails = () => {
const route = useRoute<any>();
const { id, groupName, description, contributionAmount, currentParticipants } = route.params;
const [memberData, setMemberData] = useState<Member[]>([]);
const [memberData, setMemberData] = useState([]);
const [loading, setLoading] = useState(false);
const [openModal, setOpenModal] = useState(false)
const [payable, setPayable] = useState() as any
const [openModal, setOpenModal] = useState(false);
const [payable, setPayable] = useState<any>();
const [refreshing, setRefreshing] = useState(false);
const [cycleDashboardData, setCycleDashboardData] = useState<any>();
const [selectedCycleNumber, setSelectedCycleNumber] = useState(null);
const [cycleItems, setCycleItems] = useState([]);
console.log("route param ggggggggggg : ", route?.params)
// Group object
const group = {
id: route.params.item?.id,
name: route.params.item?.groupName,
@@ -51,224 +83,291 @@ const GroupDetails = () => {
members: memberData,
};
const progress = Math.min(group.collected / group.goal, 1);
const getCycleDashboard = async () => {
try {
const response = await apiCall(`${BASE_URL}payments/groups/${group.id}/cycle-dashboard`, "GET") as any;
const responseData = response.data;
setCycleDashboardData(responseData);
// Animation refs
const animatedValues = useRef<Animated.Value[]>([]);
// useEffect(() => {
// animatedValues.current = group.members.map(() => new Animated.Value(0));
// }, [group.members]);
// useEffect(() => {
// const animations = group.members.map((_, i) =>
// Animated.timing(animatedValues.current[i], {
// toValue: 1,
// duration: 400,
// delay: i * 100,
// useNativeDriver: true,
// })
// );
// Animated.stagger(100, animations).start();
// }, [group.members]);
// Fetch group members
useEffect(() => {
const handleGroupDetails = async () => {
try {
setLoading(true);
const res = await apiCall(`${GROUP_ALL_MEMBER}?groupId=${route?.params?.item?.id}`, "GET") as any;
console.log("Group details response:", res);
setMemberData(res.data);
ToastAndroid.show("Group details fetched successfully!", ToastAndroid.SHORT);
} catch (error: any) {
console.error("Error fetching group details:", error);
ToastAndroid.show(error?.message || "Failed to fetch group details", ToastAndroid.SHORT);
} finally {
setLoading(false);
if (responseData?.cycles) {
const items = responseData.cycles.map(cycle => ({
label: `Cycle ${cycle.cycleNumber}`,
value: cycle.cycleNumber
}));
setCycleItems(items);
if (responseData.currentCycle) {
setSelectedCycleNumber(responseData.currentCycle);
}
}
};
} catch (error) {
ToastAndroid.show("Failed to fetch cycle data", ToastAndroid.SHORT);
}
};
handleGroupDetails();
}, [id]);
const fetchGroupDetails = async () => {
try {
const res = await apiCall(`${GROUP_ALL_MEMBER}?groupId=${group.id}`, "GET") as any;
console.log("Group Members Response: ", res);
setMemberData(res.data);
} catch (error: any) {
ToastAndroid.show(error?.message || "Failed to fetch group details", ToastAndroid.SHORT);
}
};
const handleContribution = async () => {
try {
const res = await apiCall(`${CONTRIBUTE}${group.id}/calculate-contribution`, 'GET') as any
setPayable(res.data)
console.log("contribution res : ", res)
const res = await apiCall(`${CONTRIBUTE}${group.id}/calculate-contribution`, 'GET') as any;
setPayable(res.data);
} catch (error) {
console.log("contribution error : ", error)
console.log("Error in handleContribution: ", error);
}
}
};
// useEffect(()=>{
// if(group?.id)
// handleContribution()
// },[])
const fetchAllData = useCallback(async () => {
setLoading(true);
await Promise.all([
fetchGroupDetails(),
handleContribution(),
getCycleDashboard()
]);
setLoading(false);
}, [group.id]);
useEffect(() => {
if (group.id) {
fetchAllData();
}
}, [group.id, fetchAllData]);
const onRefresh = useCallback(async () => {
setRefreshing(true);
await fetchAllData();
setRefreshing(false);
}, [fetchAllData]);
const confirmPayment = async () => {
try {
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${payable?.totalPayable}`, 'POST') as any
console.log("payment response : ", res)
if (res.status) {
ToastAndroid.show('Payment done!', ToastAndroid.SHORT)
}
if (res.status === 400) {
ToastAndroid.show(res?.data?.message || 'Payment done!', ToastAndroid.SHORT)
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${payable?.totalPayable}`, 'POST') as any;
if (res.status === 200 || res.status === 201) {
ToastAndroid.show('Payment done!', ToastAndroid.SHORT);
onRefresh();
} else {
ToastAndroid.show(res?.data?.message || 'Payment failed!', ToastAndroid.SHORT);
}
} catch (error) {
console.log("payment error : ", error)
ToastAndroid.show("An error occurred during payment.", ToastAndroid.SHORT);
}
};
}
const renderMember = ({ item, index }: { item: any; index: number }) => (
<View
style={[
styles.memberCard,
{
// opacity: animatedValues.current[index],
// transform: [
// {
// translateY: animatedValues.current[index].interpolate({
// inputRange: [0, 1],
// outputRange: [20, 0],
// }),
// },
// ],
borderColor: route.params?.color ? route.params?.color : colors.Home
},
]}
>
<View style={{ flexDirection: "row", gap: 10 }}>
<Ionicons name="person-circle-outline" size={40} color={route.params?.color ? route.params?.color : colors.primary} />
<View>
<Text style={styles.memberName}>
{item.firstName} {item.lastName} {item.role === "Organizer" ? "(Organizer)" : "(Member)"}
</Text>
<Text style={styles.memberDetail}>Mobile: {item.mobile}</Text>
</View>
</View>
</View>
);
const selectedCycleData = cycleDashboardData?.cycles.find(c => c.cycleNumber === selectedCycleNumber);
return (
<AppView>
<Header title={group.name} arrowColor={route.params?.color ? route.params?.color : colors.Home} titleColor={route.params?.color ? route.params?.color : colors.Home} />
<Text style={styles.description}>{group.description}</Text>
<AppView paddingHorizontal={0} style={{ flex: 1 }}>
<ScrollView
refreshControl={
<RefreshControl refreshing={refreshing || loading} onRefresh={onRefresh} colors={[colors.black]} />
}
contentContainerStyle={styles.scrollContent}
>
<Header title={group.name} />
<Text style={styles.description}>{group.description}</Text>
{/* Progress Bar */}
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View style={{ flex: progress, backgroundColor: route.params?.color ? route.params?.color : colors.Home }} />
<View style={{ flex: 1 - progress }} />
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statValue}>{group.members.length ?? 0}</Text>
<Text style={styles.statLabel}>Members</Text>
</View>
<View style={styles.statBox}>
<Text style={styles.statValue}>₹{group.collected}</Text>
<Text style={styles.statLabel}>Contribution</Text>
</View>
<View style={styles.statBox}>
<Text style={styles.statValue}>₹{group.goal}</Text>
<Text style={styles.statLabel}>Total Amount</Text>
</View>
</View>
<Text style={styles.amount}>₹{group.collected} / ₹{group.goal}</Text>
</View>
{/* Stats */}
<View style={styles.statsRow}>
<View style={styles.statBox}>
<Text style={styles.statValue}>{group.members.length ?? []}</Text>
<Text style={styles.statLabel}>Members</Text>
{cycleItems.length > 0 && (
<CustomDropdown
items={cycleItems}
selectedValue={selectedCycleNumber}
onValueChange={setSelectedCycleNumber}
placeholder="Select a Cycle"
/>
)}
{selectedCycleData && cycleDashboardData && (
<CycleDashboard data={{ ...cycleDashboardData, cycles: [selectedCycleData] }} />
)}
<View style={styles.membersHeader}>
<Text style={styles.sectionTitle}>Members</Text>
<View style={styles.statusBadge}>
<Text style={styles.statusText}>{group.status}</Text>
</View>
</View>
<View style={styles.statBox}>
<Text style={styles.statValue}>₹{group.collected}</Text>
<Text style={styles.statLabel}>Collected</Text>
</View>
<View style={styles.statBox}>
<Text style={styles.statValue}>₹{group.goal}</Text>
<Text style={styles.statLabel}>Goal</Text>
</View>
</View>
{/* Members List */}
<View style={styles.statsRow}>
<Text style={styles.sectionTitle}>Members</Text>
<Text style={[styles.sectionTitle, { backgroundColor: route.params?.color ? colors.pastelOrange : colors.pastelPink, color: route.params?.color ? colors.Settings : colors.Home, paddingHorizontal: 5 }]}>{group.status}</Text>
</View>
{memberData.map((member:any) => (
<View key={member.userId} style={styles.memberCard}>
<Ionicons name="person" size={40} color={colors.black} />
<View style={styles.memberInfo}>
<Text style={styles.memberName}>
{member.firstName} {member.role === "Organizer" ? "(Organizer)" : ""}
</Text>
</View>
</View>
))}
<FlatList
data={group.members}
keyExtractor={(item) => item.id}
renderItem={renderMember}
contentContainerStyle={styles.membersListContainer}
/>
{/* Actions */}
{
group.status === 'ACTIVE' &&
<View style={styles.actions}>
<TouchableOpacity onPress={() => {
setOpenModal(true)
handleContribution()
}}
style={[styles.button, { backgroundColor: route.params?.color ? route.params?.color : colors.Home }]}>
{/* Button at the bottom inside scroll (no absolute positioning) */}
{group.status === 'ACTIVE' && !payable?.hasPaid && (
<TouchableOpacity onPress={() => setOpenModal(true)} style={styles.button}>
<Text style={styles.buttonText}>Contribute</Text>
</TouchableOpacity>
</View>
}
{
openModal && <ContributionScreen data={payable} visible={openModal} onClose={() => setOpenModal(!openModal)} onConfirm={() => {
confirmPayment()
setOpenModal(false)
}} />
}
)}
</ScrollView>
{openModal && (
<ContributionScreen
data={payable}
visible={openModal}
onClose={() => setOpenModal(false)}
onConfirm={() => {
confirmPayment();
setOpenModal(false);
}}
/>
)}
</AppView>
);
};
export default GroupDetails;
// Styles remain mostly unchanged
const styles = StyleSheet.create({
dropdownWrapper: {
marginHorizontal: 15,
marginBottom: 20,
},
dropdownHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 12,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
backgroundColor: '#fff',
},
modalOverlay: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'rgba(0,0,0,0.4)',
padding: 20,
},
dropdownListContainer: {
backgroundColor: '#fff',
borderRadius: 8,
padding: 10,
maxHeight: 300,
},
dropdownItem: {
paddingVertical: 12,
paddingHorizontal: 10,
},
selectedItem: {
backgroundColor: colors.pastelOrange,
borderRadius: 6,
},
itemText: {
fontSize: 15,
color: colors.black,
},
description: {
fontSize: 14,
color: "#475569",
marginBottom: 20,
lineHeight: 20,
paddingHorizontal: 15,
},
progressContainer: { marginBottom: 24 },
progressBar: {
statsRow: {
flexDirection: "row",
height: 8,
borderRadius: 4,
backgroundColor: "#e2e8f0",
overflow: "hidden",
marginBottom: 6,
justifyContent: "space-around",
marginBottom: 20,
paddingHorizontal: 15,
},
amount: { fontSize: 13, color: "#64748b" },
statsRow: { flexDirection: "row", justifyContent: "space-between", marginBottom: 20 },
statBox: {
alignItems: 'center',
flex: 1,
backgroundColor: "#fff",
marginHorizontal: 4,
padding: 16,
borderRadius: 12,
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.05,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 6,
},
statValue: {
fontSize: 18,
fontWeight: '700',
color: '#1e293b',
},
statLabel: {
fontSize: 12,
color: '#64748b',
marginTop: 4,
},
membersHeader: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: 'center',
paddingHorizontal: 15,
marginTop: 20,
marginBottom: 10,
},
sectionTitle: {
fontSize: 18,
fontWeight: "600",
color: "#0f172a",
},
statusBadge: {
backgroundColor: colors.pastelOrange,
borderRadius: 20,
paddingHorizontal: 12,
paddingVertical: 6,
elevation: 2,
},
statValue: { fontSize: 18, fontWeight: "700", color: "#1e293b" },
statLabel: { fontSize: 12, color: "#64748b", marginTop: 4 },
sectionTitle: { fontSize: 16, fontWeight: "600", color: "#0f172a", marginBottom: 12 },
membersListContainer: { paddingBottom: 20 },
statusText: {
fontSize: 14,
fontWeight: '600',
color: colors.Home,
},
memberCard: {
backgroundColor: "#fff",
padding: 16,
flexDirection: "row",
alignItems: 'center',
backgroundColor: '#fff',
padding: 12,
borderRadius: 12,
marginBottom: 10,
borderLeftWidth: 5,
elevation: 2,
marginHorizontal: 15,
},
memberInfo: {
marginLeft: 10,
},
memberName: {
fontSize: 15,
fontWeight: "600",
color: "#1e293b",
},
button: {
backgroundColor: colors.pastelOrange,
marginHorizontal: 15,
marginTop: 20,
marginBottom: 30,
elevation: 5,
paddingVertical: 14,
borderRadius: 10,
alignItems: "center",
},
buttonText: {
fontSize: 16,
fontWeight: "600",
color: "#000",
},
scrollContent: {
paddingBottom: 50,
},
memberName: { fontSize: 15, fontWeight: "600", color: "#1e293b", marginBottom: 4 },
memberDetail: { fontSize: 13, color: "#475569" },
actions: { marginTop: 16 },
button: { backgroundColor: colors.Home, paddingVertical: 14, borderRadius: 10, alignItems: "center", marginBottom: 12 },
buttonText: { fontSize: 15, fontWeight: "600", color: "#fff" },
});
+2 -1
View File
@@ -127,7 +127,8 @@ const ManagementScreen: FC = () => {
case "My Groups":
return <FlatList data={myGroups} renderItem={({ item }) => <MyGroupCard item={item} onManage={handleManageGroup} />} keyExtractor={(item) => item.id} ListEmptyComponent={<Text style={styles.emptyText}>You haven't created any groups yet.</Text>} onRefresh={fetchData} refreshing={loading} contentContainerStyle={{ paddingBottom: 100 }} />;
case "Joined Groups":
return <FlatList data={joinedGroups} renderItem={({ item }) => <JoinedGroupCard item={item} onViewPress={() => handleViewGroup(item)} />} keyExtractor={(item) => item.id} ListEmptyComponent={<Text style={styles.emptyText}>You haven't joined any groups yet.</Text>} onRefresh={fetchData} refreshing={loading} contentContainerStyle={{ paddingBottom: 100 }} />;
return <FlatList data={joinedGroups} renderItem={({ item }) => <JoinedGroupCard item={item} onViewPress={() => {
handleViewGroup(item)}} />} keyExtractor={(item) => item.id} ListEmptyComponent={<Text style={styles.emptyText}>You haven't joined any groups yet.</Text>} onRefresh={fetchData} refreshing={loading} contentContainerStyle={{ paddingBottom: 100 }} />;
case "Requests":
return <FlatList data={requests} renderItem={({ item }) => <RequestCard item={item} onApprove={handleApprove} onDecline={handleDecline} />} keyExtractor={(item) => item.requestId} ListEmptyComponent={<Text style={styles.emptyText}>No pending join requests.</Text>} onRefresh={fetchData} refreshing={loading} contentContainerStyle={{ paddingBottom: 100 }} />;
case "Your Requests":