enovate new dashboard lik a real time application

This commit is contained in:
Jaanu423
2025-10-14 11:27:27 +05:30
20 changed files with 540 additions and 326 deletions
+58 -41
View File
@@ -32,6 +32,7 @@ import { StackNavigationProp } from '@react-navigation/stack';
import WinnerUser from './WinnerUser';
// --- Type Definitions ---
interface Participant {
id: string;
@@ -42,6 +43,7 @@ interface Participant {
}
export interface GroupDetails {
id: string;
groupName: string;
@@ -63,15 +65,16 @@ export interface GroupDetails {
organizerId: string;
totalParticipants: number;
participants: Participant[];
imageBlob: string
imageBlob: string;
}
export interface EditGroupPayload {
groupName: string;
description: string;
maxParticipants: number;
minParticipants?:number;
minParticipants?: number;
contributionAmount: number;
customCycleDays: number | null;
cycleType: 'QUARTERLY' | 'MONTHLY' | 'WEEKLY' | 'CUSTOM';
@@ -82,6 +85,7 @@ export interface EditGroupPayload {
}
// Correct props for a screen inside a nested Tab Navigator
type EditGroupScreenProps = CompositeScreenProps<
BottomTabScreenProps<RootStackParamList, 'EditGroup'>,
@@ -89,16 +93,20 @@ type EditGroupScreenProps = CompositeScreenProps<
>;
type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null;
type GroupManagementNavProp = StackNavigationProp<RootStackParamList, 'EditGroup'>;
// --- Main Screen Component ---
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const { groupId } = route?.params;
console.log("edit group screen groupId : ", route?.params)
console.log("edit group screen groupId : ", route?.params)
const navigation = useNavigation<GroupManagementNavProp>();
const [editingCard, setEditingCard] = useState<EditingCard>(null);
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
const [editableData, setEditableData] = useState<EditGroupPayload>({
@@ -114,6 +122,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
lateFeeValue: null,
});
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [starting, setStarting] = useState(false);
@@ -141,6 +150,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
};
const fetchGroupDetails = useCallback(async () => {
setLoading(true);
setError(null);
@@ -148,6 +158,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const response = await apiCall(`${BASE_URL}fund/group/details?groupId=${groupId}`, 'GET') as { status: string; data?: GroupDetails; message?: string };
console.log("groupDetails", response)
if (response.status === 'success' && response.data) {
setGroupDetails(response.data);
initializeEditableData(response.data);
@@ -160,14 +171,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
setLoading(false);
}
}, [groupId]);
// const getPaymentStatus = async()=>{
// const response = await apiCall(`${BASE_URL}payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number ,status: boolean }; message?: string };
// console.log("paymentStatus",response)
// if (response.status === 'success') {
// setPaymentStatus(response.data?.status || false);
// setCurrentCycle(response.data?.currentCycle || null);
// }
// }
const getPaymentStatus = async () => {
const response = await apiCall(`${BASE_URL}payments/group/${groupId}/payment/paid`, 'GET') as { status: string; data?: { currentCycle: number, allPaid: boolean, selectedUser: any }; message?: string };
@@ -179,16 +182,19 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
}
console.log("groupid : ", groupId)
useEffect(() => {
getPaymentStatus()
}, [])
useEffect(() => {
fetchGroupDetails();
}, [fetchGroupDetails]);
const handleCancel = () => {
if (groupDetails) {
initializeEditableData(groupDetails);
@@ -197,10 +203,10 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
};
const handleSave = async () => {
if (!groupDetails) return;
if (editableData.maxParticipants < groupDetails.totalParticipants) {
ToastAndroid.show(`Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`, ToastAndroid.SHORT);
return;
@@ -208,8 +214,10 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
setSaving(true);
try {
await apiCall(`${BASE_URL}fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
ToastAndroid.show('Group has been updated.', ToastAndroid.SHORT);
const res = await apiCall(`${BASE_URL}fund/groups/${groupDetails.id}/edit`, 'PUT', editableData);
console.log("Edit Group Response: ", res,editableData)
Alert.alert('Success', 'Group has been updated.');
setEditingCard(null);
await fetchGroupDetails();
} catch (e: any) {
@@ -220,16 +228,19 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
};
const handleDistribute = async () => {
navigation.navigate('RandomSelectUserScreen', { groupId , cycleNumber: currentCycle || 0 });
navigation.navigate('RandomSelectUserScreen', { groupId, cycleNumber: currentCycle || 0 });
};
const handleStartGroup = async () => {
if (!groupDetails) return;
Alert.alert(
"Confirm Start",
"Are you sure you want to start this group? This action cannot be undone.",
@@ -256,13 +267,13 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
);
};
const handleInvite = async () => {
if (!groupDetails) return;
setInviting(true);
try {
const response = await apiCall(`${BASE_URL}invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
const inviteLink = response.data;
if (inviteLink) {
@@ -284,12 +295,16 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
};
const handleUpdateField = (field: keyof EditGroupPayload, value: any) => {
setEditableData(prev => ({ ...prev, [field]: value }));
};
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean, isEditable?: boolean) => {
// --- CORRECTED FUNCTION ---
// The 'isEditable' parameter was removed, and the condition now correctly
// uses 'isEditing' to show the input fields.
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => {
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
const getInputForField = () => {
@@ -328,7 +343,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
return (
<View style={styles.fieldContainer}>
<Text style={styles.infoLabel}>{label}</Text>
{isEditing && isEditable ? (
{isEditing ? (
<View style={{ marginTop: 8 }}>{getInputForField()}</View>
) : (
<Text style={styles.infoValue}>{displayValue}</Text>
@@ -337,9 +352,11 @@ 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;
// This isEditable check correctly controls the visibility of the "Update" button itself.
const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED';
return (
<View style={styles.card}>
@@ -353,6 +370,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View>
{fields.map(({ field, label, displayValue }) =>
// The call now correctly matches the updated renderField signature
renderField(field, label, displayValue(), isEditingThisCard)
)}
@@ -370,15 +388,16 @@ 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;
console.log(" myu status : ", groupDetails.status)
const pickImage = () => {
@@ -399,7 +418,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const maxSizeKB = 1000;
const maxSizeBytes = maxSizeKB * 1024;
// Size validation
if (selectedImage.fileSize && selectedImage.fileSize > maxSizeBytes) {
const currentSizeKB = (selectedImage.fileSize / 1024).toFixed(2);
Alert.alert(
@@ -409,23 +427,15 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
return;
}
// Additional validation for required fields
if (!selectedImage.uri) {
ToastAndroid.show("Invalid image file", ToastAndroid.SHORT);
return;
}
// console.log("✅ Image selected:", {
// uri: selectedImage.uri,
// size: (selectedImage?.fileSize / 1024).toFixed(2) + "KB",
// width: selectedImage.width,
// height: selectedImage.height
// });
setSelectedImage(selectedImage.uri);
});
};
const handleUpdateImage = async () => {
try {
const credentials = await Keychain.getGenericPassword();
@@ -435,16 +445,18 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
const token = credentials.password;
if (!selectedImage) {
ToastAndroid.show("Please select an image first", ToastAndroid.SHORT);
return;
}
setImageUpdating(true);
// Get file extension and mime type dynamically
const fileExtension = selectedImage.split('.').pop();
const formData = new FormData();
formData.append("file", {
uri: selectedImage,
@@ -452,12 +464,13 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
});
const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
console.log("url : ", url)
console.log("formdata : ", formData)
// return
const response = await fetch(url, {
method: "PUT",
headers: {
@@ -467,12 +480,14 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
body: formData,
});
console.log("image response : ", response)
if (response?.status === 200) {
fetchGroupDetails()
ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
}
} catch (error: any) {
console.error("❌ Error updating group image:", error);
} finally {
@@ -480,6 +495,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
};
const getProfileImage = () => {
return selectedImage
? selectedImage
@@ -487,8 +503,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
? `data:image/jpeg;base64,${groupDetails.imageBlob}`
: "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
};
// Helper function to get MIME type
return (
<AppView style={{ flex: 1, paddingHorizontal: 0 }}>
@@ -501,7 +515,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
)}
</View>
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
@@ -510,7 +523,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
<View style={[styles.avatarContainer, { flexDirection: 'row', flex: 1, gap: 10 }]}>
<View style={{}}>
<Image
// source={{ uri: groupDetails?.imageBlob ? groupDetails?.imageBlob : selectedImage || "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg" }}
source={{ uri: getProfileImage() }}
style={styles.avatar}
onError={() => console.log("Error loading image")}
@@ -535,6 +547,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
], 'groupInfo')}
{groupDetails.status === 'ACTIVE' ?
renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
@@ -551,12 +564,14 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
{renderCard('Configuration', [
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
...(groupDetails.cycleType === 'CUSTOM' ? [{ field: 'customCycleDays' as keyof EditGroupPayload, label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays} days` }] : []),
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
], 'configuration')}
<View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
{groupDetails.participants.length > 0 ? (
@@ -577,7 +592,6 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View>
</ScrollView>
{canStartGroup && (
<View style={styles.floatingButtonContainer}>
<TouchableOpacity
@@ -593,7 +607,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</TouchableOpacity>
</View>
)}
{selectedUser?.cycleStatus !== "PENDING_PAYMENTS" && groupDetails.status === 'ACTIVE' && selectedUser?.cycleStatus === "PENDING_SELECTION" &&(
{selectedUser?.cycleStatus !== "PENDING_PAYMENTS" && groupDetails.status === 'ACTIVE' && selectedUser?.cycleStatus === "PENDING_SELECTION" && (
<View style={styles.floatingButtonContainer}>
<TouchableOpacity
style={styles.activateButton}
@@ -611,7 +625,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}
{
selectedUser?.selectedUser !== null && selectedUser?.cycleStatus === "READY_FOR_WITHDRAWAL" &&
selectedUser?.selectedUser !== null && selectedUser?.cycleStatus === "READY_FOR_WITHDRAWAL" &&
<View style={styles.floatingButtonContainer}>
<CustomButton
buttonBackground={colors?.pastelBlue}
@@ -630,6 +644,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' },
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
@@ -647,6 +662,7 @@ const styles = StyleSheet.create({
},
avatarContainer: {
position: "relative",
marginVertical: 16,
@@ -682,8 +698,8 @@ const styles = StyleSheet.create({
marginBottom: 12,
},
cardTitle: { fontSize: 18, fontWeight: 'bold', color: '#1F2937' },
editButton: { padding: 8,backgroundColor:colors.pastelOrange, borderRadius: 15,elevation:5 },
editButtonText: { color: colors.black, fontSize: 15, fontWeight: 'bold' },
editButton: { padding: 8, backgroundColor: colors.pastelOrange, borderRadius: 15, elevation: 5 },
editButtonText: { color: colors.black, fontSize: 15, fontWeight: 'bold' },
fieldContainer: { borderBottomWidth: 1, borderBottomColor: '#F3F4F6', paddingBottom: 12, marginBottom: 12 },
infoLabel: { fontSize: 16, color: '#6B7280', marginBottom: 4 },
infoValue: { fontSize: 16, fontWeight: '600', color: '#111827', textTransform: 'capitalize' },
@@ -731,4 +747,5 @@ const styles = StyleSheet.create({
});
export default EditGroupScreen;