374 lines
12 KiB
TypeScript
374 lines
12 KiB
TypeScript
import React, { useEffect, useState, useCallback } from "react";
|
|
import {
|
|
StyleSheet,
|
|
Text,
|
|
View,
|
|
TouchableOpacity,
|
|
ToastAndroid,
|
|
RefreshControl,
|
|
ScrollView,
|
|
Modal,
|
|
} from "react-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";
|
|
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";
|
|
|
|
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;
|
|
|
|
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 [memberData, setMemberData] = useState([]);
|
|
const [loading, setLoading] = useState(false);
|
|
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([]);
|
|
|
|
const group = {
|
|
id: route.params.item?.id,
|
|
name: route.params.item?.groupName,
|
|
collected: route.params.item?.contributionAmount,
|
|
goal: route.params.item?.contributionAmount * route.params.item?.currentParticipants,
|
|
description: route.params.item?.description,
|
|
status: route.params.item?.status,
|
|
members: memberData,
|
|
};
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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);
|
|
} catch (error) {
|
|
console.log("Error in handleContribution: ", error);
|
|
}
|
|
};
|
|
|
|
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;
|
|
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) {
|
|
ToastAndroid.show("An error occurred during payment.", ToastAndroid.SHORT);
|
|
}
|
|
};
|
|
|
|
const selectedCycleData = cycleDashboardData?.cycles.find(c => c.cycleNumber === selectedCycleNumber);
|
|
|
|
return (
|
|
<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>
|
|
|
|
<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>
|
|
|
|
{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>
|
|
|
|
{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>
|
|
))}
|
|
|
|
{/* 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>
|
|
)}
|
|
</ScrollView>
|
|
|
|
{openModal && (
|
|
<ContributionScreen
|
|
data={payable}
|
|
visible={openModal}
|
|
onClose={() => setOpenModal(false)}
|
|
onConfirm={() => {
|
|
confirmPayment();
|
|
setOpenModal(false);
|
|
}}
|
|
/>
|
|
)}
|
|
</AppView>
|
|
);
|
|
};
|
|
|
|
export default GroupDetails;
|
|
|
|
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,
|
|
},
|
|
statsRow: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-around",
|
|
marginBottom: 20,
|
|
paddingHorizontal: 15,
|
|
},
|
|
statBox: {
|
|
alignItems: 'center',
|
|
flex: 1,
|
|
},
|
|
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,
|
|
},
|
|
statusText: {
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: colors.Home,
|
|
},
|
|
memberCard: {
|
|
flexDirection: "row",
|
|
alignItems: 'center',
|
|
backgroundColor: '#fff',
|
|
padding: 12,
|
|
borderRadius: 12,
|
|
marginBottom: 10,
|
|
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,
|
|
},
|
|
});
|