278 lines
8.7 KiB
TypeScript
278 lines
8.7 KiB
TypeScript
import React, { FC } from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity, ToastAndroid } from 'react-native';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import { responsiveFontSize } from 'react-native-responsive-dimensions';
|
|
import { useTheme } from '../hooks/ui/useTheme';
|
|
import { useFetch } from '../hooks/core/useFetch';
|
|
import { useCreate } from '../hooks/core/useCreate';
|
|
import { CONTRIBUTE } from '../utils/ApiUrls';
|
|
import FontsFamily from '../constents/FontsFamily';
|
|
|
|
interface JoinedGroup {
|
|
id: string;
|
|
groupName: string;
|
|
description: string;
|
|
visibility: 'PUBLIC' | 'PRIVATE';
|
|
createdByUserId: number;
|
|
createdByName: string;
|
|
startDate: string | null;
|
|
endDate: string | null;
|
|
minParticipants: number;
|
|
maxParticipants: number;
|
|
currentParticipants: number;
|
|
contributionAmount: number;
|
|
cycleType: 'MONTHLY' | 'WEEKLY' | 'QUARTERLY' | 'CUSTOM';
|
|
status: 'ACTIVE' | 'PENDING' | 'COMPLETED'| 'APPROVED';
|
|
}
|
|
|
|
interface JoinedGroupCardProps {
|
|
item: JoinedGroup;
|
|
hasPaid?: boolean;
|
|
}
|
|
|
|
const formatDate = (dateStr: string | null): string => {
|
|
if (!dateStr) return 'N/A';
|
|
const [year, month, day] = dateStr.split('-');
|
|
const months = [
|
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
|
];
|
|
const monthIndex = parseInt(month, 10) - 1;
|
|
const dayNum = parseInt(day, 10);
|
|
return `${dayNum} ${months[monthIndex]} ${year}`;
|
|
};
|
|
|
|
export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item }:any) => {
|
|
const theme = useTheme();
|
|
const navigation = useNavigation<any>();
|
|
console.log("item",item)
|
|
|
|
const { data: payableRes } = useFetch<any>(`${CONTRIBUTE}${item.id}/calculate-contribution`);
|
|
const payable = payableRes?.data;
|
|
|
|
const { mutate: contribute, isPending: loading } = useCreate<any, any>(
|
|
`payments/contribute/${item?.id}?amount=${payable?.totalPayable}`,
|
|
{
|
|
onSuccess: () => {
|
|
ToastAndroid.show('Payment successful!', ToastAndroid.SHORT);
|
|
},
|
|
onError: () => {
|
|
ToastAndroid.show('Payment failed. Please try again.', ToastAndroid.SHORT);
|
|
},
|
|
}
|
|
);
|
|
|
|
const getStatusColors = (status: JoinedGroup['status']) => {
|
|
switch (status) {
|
|
case 'ACTIVE':
|
|
return { bg: theme.palette.greyExtraLight, text: theme.palette.black };
|
|
case 'COMPLETED':
|
|
return { bg: theme.palette.greyLight, text: theme.palette.greyDark };
|
|
default:
|
|
return { bg: theme.palette.greyExtraLight, text: theme.palette.greyDark };
|
|
}
|
|
};
|
|
|
|
const statusColors = getStatusColors(item.status);
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.card, { backgroundColor: theme.colors.card, borderColor: theme.colors.border }]}
|
|
onPress={() => navigation.navigate('GroupDetails', { item })}
|
|
activeOpacity={0.7}
|
|
>
|
|
<View style={styles.header}>
|
|
<View style={styles.titleContainer}>
|
|
<Text style={[styles.groupName, { color: theme.colors.textPrimary }]}>{item.groupName}</Text>
|
|
<Text style={[styles.createdBy, { color: theme.colors.textSecondary }]}>
|
|
by {item.createdByName}
|
|
</Text>
|
|
</View>
|
|
<View style={[styles.statusBadge, { backgroundColor: statusColors.bg }]}>
|
|
<Text style={[styles.statusText, { color: statusColors.text }]}>{item.status}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Contribution Amount — always visible */}
|
|
<View style={[styles.infoRow, { borderColor: theme.colors.border }]}>
|
|
<View style={styles.infoItem}>
|
|
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Contribution</Text>
|
|
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>
|
|
UGX {item.contributionAmount.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.infoItem}>
|
|
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Duration</Text>
|
|
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>{`${item?.cycleType} (${item?.customCycleDays} days)`}</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Payment Details — shown when NOT paid and there's an amount due */}
|
|
{payable && !payable.hasPaid && payable.totalPayable > 0 && (
|
|
<View style={styles.payableSection}>
|
|
<View style={styles.payableRow}>
|
|
<Text style={[styles.payableLabel, { color: theme.colors.textSecondary }]}>Total Payable</Text>
|
|
<Text style={[styles.payableValue, { color: theme.colors.textPrimary }]}>
|
|
UGX {payable.totalPayable.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
{payable.effectiveLateFee > 0 && (
|
|
<View style={styles.payableRow}>
|
|
<Text style={[styles.payableLabel, { color: theme.colors.warning }]}>Late Fee</Text>
|
|
<Text style={[styles.payableValue, { color: theme.colors.warning }]}>
|
|
+ UGX {payable.effectiveLateFee.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{payable.alreadyPaid > 0 && (
|
|
<View style={styles.payableRow}>
|
|
<Text style={[styles.payableLabel, { color: theme.colors.success }]}>Already Paid</Text>
|
|
<Text style={[styles.payableValue, { color: theme.colors.success }]}>
|
|
UGX {payable.alreadyPaid.toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
)}
|
|
|
|
<View style={styles.footer}>
|
|
<Text style={[styles.dateText, { color: theme.colors.textMuted }]}>
|
|
{formatDate(item.startDate)} — {formatDate(item.endDate)}
|
|
</Text>
|
|
|
|
{payable?.hasPaid ? (
|
|
<View style={[styles.paidBadge, { backgroundColor: theme.colors.success + '18' }]}>
|
|
<Text style={[styles.paidText, { color: theme.colors.success }]}>✓ PAID</Text>
|
|
</View>
|
|
) : (
|
|
item.status !== 'COMPLETED' && (
|
|
<TouchableOpacity
|
|
style={[styles.payButton, { backgroundColor: theme.colors.primary }]}
|
|
onPress={() => {
|
|
navigation.navigate('PaymentMode' as any, {
|
|
groupId: item.id,
|
|
routeFrom: 'GroupDetails'
|
|
})
|
|
}}
|
|
disabled={loading}
|
|
>
|
|
<Text style={[styles.payButtonText, { color: theme.colors.onPrimary }]}>
|
|
{loading ? '...' : 'Pay'}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
)
|
|
)}
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
borderRadius: 16,
|
|
padding: 14,
|
|
marginBottom: 12,
|
|
marginHorizontal: 12,
|
|
borderWidth: 1,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 8,
|
|
elevation: 2,
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
marginBottom: 10,
|
|
},
|
|
titleContainer: {
|
|
flex: 1,
|
|
},
|
|
groupName: {
|
|
fontSize: responsiveFontSize(2),
|
|
fontFamily: FontsFamily.nunitoExtraBold,
|
|
marginBottom: 2,
|
|
},
|
|
createdBy: {
|
|
fontSize: responsiveFontSize(1.4),
|
|
fontFamily: FontsFamily.nunitoRegular,
|
|
},
|
|
statusBadge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
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,
|
|
},
|
|
payableSection: {
|
|
marginBottom: 12,
|
|
gap: 6,
|
|
},
|
|
payableRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
},
|
|
payableLabel: {
|
|
fontSize: responsiveFontSize(1.35),
|
|
fontFamily: FontsFamily.nunitoSemiBold,
|
|
},
|
|
payableValue: {
|
|
fontSize: responsiveFontSize(1.5),
|
|
fontFamily: FontsFamily.nunitoBold,
|
|
},
|
|
footer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginTop: 4,
|
|
},
|
|
dateText: {
|
|
fontSize: responsiveFontSize(1.3),
|
|
fontFamily: FontsFamily.nunitoMedium,
|
|
},
|
|
payButton: {
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
borderRadius: 8,
|
|
minWidth: 80,
|
|
alignItems: 'center',
|
|
},
|
|
payButtonText: {
|
|
fontSize: responsiveFontSize(1.5),
|
|
fontFamily: FontsFamily.nunitoBold,
|
|
},
|
|
paidBadge: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 6,
|
|
borderRadius: 6,
|
|
},
|
|
paidText: {
|
|
fontSize: responsiveFontSize(1.3),
|
|
fontFamily: FontsFamily.nunitoBold,
|
|
letterSpacing: 0.5,
|
|
},
|
|
});
|