fund distribution

This commit is contained in:
Abin Toppo
2025-09-26 10:16:19 +05:30
parent a303af566e
commit 6778e79dc8
6 changed files with 630 additions and 248 deletions
+49 -23
View File
@@ -21,6 +21,7 @@ import { CompositeScreenProps } from '@react-navigation/native';
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
// --- Type Definitions ---
interface Participant {
id: string;
@@ -30,6 +31,7 @@ interface Participant {
role: 'ORGANIZER' | 'USER';
}
export interface GroupDetails {
id: string;
groupName: string;
@@ -53,6 +55,7 @@ export interface GroupDetails {
participants: Participant[];
}
export interface EditGroupPayload {
groupName: string;
description: string;
@@ -66,18 +69,22 @@ export interface EditGroupPayload {
lateFeeValue: number | null;
}
// Correct props for a screen inside a nested Tab Navigator
type EditGroupScreenProps = CompositeScreenProps<
BottomTabScreenProps<ManagementTabParamList, 'EditGroup'>,
NativeStackScreenProps<RootStackParamList>
>;
type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null;
// --- Main Screen Component ---
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const { groupId } = route.params;
const [editingCard, setEditingCard] = useState<EditingCard>(null);
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
const [editableData, setEditableData] = useState<EditGroupPayload>({
@@ -99,7 +106,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const [inviting, setInviting] = useState(false);
const [error, setError] = useState<string | null>(null);
// ... (All your handler functions: initializeEditableData, fetchGroupDetails, handleCancel, handleSave, etc. remain the same)
const initializeEditableData = (data: GroupDetails) => {
setEditableData({
groupName: data.groupName,
@@ -115,6 +122,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
});
};
const fetchGroupDetails = useCallback(async () => {
setLoading(true);
setError(null);
@@ -134,10 +142,12 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
}, [groupId]);
useEffect(() => {
fetchGroupDetails();
}, [fetchGroupDetails]);
const handleCancel = () => {
if (groupDetails) {
initializeEditableData(groupDetails);
@@ -145,9 +155,11 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
setEditingCard(null);
};
const handleSave = async () => {
if (!groupDetails) return;
if (editableData.maxParticipants < groupDetails.totalParticipants) {
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
return;
@@ -166,9 +178,11 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
};
const handleStartGroup = async () => {
if (!groupDetails) return;
Alert.alert(
"Confirm Start",
"Are you sure you want to start this group? This action cannot be undone.",
@@ -200,6 +214,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
try {
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
const inviteLink = response.data;
if (inviteLink) {
@@ -220,12 +235,13 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
};
const handleUpdateField = (field: keyof EditGroupPayload, value: any) => {
setEditableData(prev => ({ ...prev, [field]: value }));
};
// ... (renderField and renderCard functions also remain the same)
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => {
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean, isEditable: boolean) => {
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
const getInputForField = () => {
@@ -276,6 +292,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => {
const isEditingThisCard = editingCard === cardKey;
return (
<View style={styles.card}>
<View style={styles.cardHeader}>
@@ -288,7 +305,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View>
{fields.map(({ field, label, displayValue }) =>
renderField(field, label, displayValue(), isEditingThisCard)
renderField(field, label, displayValue(), isEditingThisCard, isEditable)
)}
{isEditingThisCard && (
@@ -305,16 +322,18 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
);
}
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>;
if (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
if (!groupDetails) return null;
const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED';
const isInvitable = groupDetails.status === 'APPROVED';
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
return (
// Use AppView as the root container with flex: 1
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
<View style={styles.headerContainer}>
<Text style={styles.headerTitle}>Manage Group</Text>
@@ -325,6 +344,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
)}
</View>
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
@@ -335,20 +355,29 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description },
], 'groupInfo')}
{renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
], 'financialRules')}
{groupDetails.status === 'ACTIVE' ?
renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
{ field: 'maxParticipants', label: 'Current Participants', displayValue: () => `${groupDetails.totalParticipants}` },
], 'financialRules')
:
renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
], 'financialRules')
}
{renderCard('Configuration', [
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
{ field: 'customCycleDays', label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays ?? ''} days` },
...(groupDetails.cycleType === 'CUSTOM' ? [{ field: 'customCycleDays' as keyof EditGroupPayload, label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays} days` }] : []),
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
], 'configuration')}
{/* Participants Card ... */}
<View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
{groupDetails.participants.length > 0 ? (
@@ -369,8 +398,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View>
</ScrollView>
{/* --- FLOATING BUTTON AREA --- */}
{/* This View is a sibling of ScrollView, allowing it to float on top */}
{canStartGroup && (
<View style={styles.floatingButtonContainer}>
<TouchableOpacity
@@ -390,10 +418,9 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
);
};
// --- Styles ---
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' },
// Added more paddingBottom to ensure content doesn't hide behind the button AND the tab bar
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
@@ -437,15 +464,13 @@ const styles = StyleSheet.create({
cancelButton: { backgroundColor: '#E5E7EB' },
inviteButton: { backgroundColor: colors.primary },
buttonText: { color: '#ffffffff', fontSize: 15, fontWeight: '600' },
// --- FLOATING BUTTON STYLES ---
floatingButtonContainer: {
position: 'absolute',
bottom: 85, // Positioned above the tab bar
bottom: 85,
left: 20,
right: 20,
alignItems: 'center', // Center the button horizontally
zIndex: 1, // Ensure it's on top
alignItems: 'center',
zIndex: 1,
},
activateButton: {
backgroundColor: colors.pastelOrange,
@@ -457,7 +482,7 @@ const styles = StyleSheet.create({
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 4,
width: '90%', // Adjust width as needed
width: '90%',
alignItems: 'center',
},
activateButtonText: {
@@ -467,4 +492,5 @@ const styles = StyleSheet.create({
},
});
export default EditGroupScreen;