homePage update

This commit is contained in:
Abin Toppo
2025-09-25 15:46:17 +05:30
18 changed files with 1079 additions and 508 deletions
+71 -183
View File
@@ -1,6 +1,6 @@
// src/screens/ManagementScreen.tsx
import React, { useState, useCallback, FC } from "react";
import React, { useState, useCallback, FC, useEffect } from "react";
import {
View,
Text,
@@ -9,69 +9,74 @@ import {
TouchableOpacity,
ActivityIndicator,
Alert,
ListRenderItemInfo,
} from "react-native";
import { useNavigation, useFocusEffect } from "@react-navigation/native";
import { useNavigation, useFocusEffect, useRoute, RouteProp } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../../navigation/AppNavigator";
import { APPROVE_REQUEST_URL, BASE_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, MY_GROUPS_URL, REQUESTS_URL, USER_REQUEST } from "../../utils/ApiUrls";
import { APPROVE_REQUEST_URL, DECLINE_REQUEST_URL, GET_JOINED_GROUP_URL, MY_GROUPS_URL, REQUESTS_URL, USER_REQUEST } from "../../utils/ApiUrls";
import colors from "../../components/colors";
import AppView from "../../components/AppView";
import { apiCall } from "../../utils/ApiHelper";
import * as Keychain from "react-native-keychain";
import { MyGroup, JoinedGroup, JoinRequest, ApiResponse, UserJoinRequest } from '../../types';
import { MyGroup, JoinedGroup, JoinRequest, ApiResponse, UserJoinRequest } from '../../types'; // Adjust import path
import { MyGroupCard } from "../../components/MyGroupCard";
import { JoinedGroupCard } from "../../components/JoinedGroupCard";
import { RequestCard } from "../../components/RequestCard";
import { useSelector } from "react-redux";
import Ionicons from "@react-native-vector-icons/ionicons";
import { UserRequested } from "../../components/UserRequested";
import { RootTabParamList } from "../../navigation/BottomTabs"; // Adjust import path
type GroupManagementNavProp = StackNavigationProp<RootStackParamList>;
// --- TYPE DEFINITIONS ---
type GroupManagementNavProp = StackNavigationProp<RootTabParamList, 'Management'>;
type ManagementRouteProp = RouteProp<RootTabParamList, 'Management'>;
type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
const ManagementScreen: FC = () => {
const userRole = useSelector((state: any) => state.user)
const [activeTab, setActiveTab] = useState<ActiveTab>(userRole.role === "USER" ? "Joined Groups" : "My Groups");
const user = useSelector((state: any) => state.user);
const navigation = useNavigation<GroupManagementNavProp>();
const route = useRoute<ManagementRouteProp>();
const [activeTab, setActiveTab] = useState<ActiveTab>(user.role === "USER" ? "Joined Groups" : "My Groups");
const [myGroups, setMyGroups] = useState<MyGroup[]>([]);
const [joinedGroups, setJoinedGroups] = useState<JoinedGroup[]>([]);
const [requests, setRequests] = useState<JoinRequest[]>([]);
const [userRequest, setUserRequest] = useState<UserJoinRequest[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [userRequest, setUserRequest] = useState<UserJoinRequest[]>([])
// --- HOOKS ---
// Effect to set the initial tab from route params
useEffect(() => {
const passedTab = route.params?.actionTab;
if (passedTab) {
const availableTabs = user.role === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"];
if (availableTabs.includes(passedTab)) {
setActiveTab(passedTab);
}
}
}, [route.params?.actionTab, user.role]);
// --- FIXED: fetchData dependency array is corrected ---
const fetchData = useCallback(async () => {
// Check for loading state at the start to prevent multiple simultaneous fetches
if (loading) return;
setLoading(true);
setError(null);
try {
if (userRole.role === "ORGANIZER") {
// Fetch all data concurrently for organizers
if (user.role === "ORGANIZER") {
const [myGroupsRes, joinedGroupsRes, requestsRes] = await Promise.all([
apiCall(MY_GROUPS_URL, "GET") as Promise<ApiResponse<MyGroup[]>>,
apiCall(GET_JOINED_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>,
apiCall(REQUESTS_URL, "GET") as Promise<ApiResponse<JoinRequest[]>>,
]);
console.log("My Groups:", myGroupsRes.data);
console.log("Joined Groups:", joinedGroupsRes.data);
console.log("Requests:", requestsRes.data);
setMyGroups(myGroupsRes.data || []);
setJoinedGroups(joinedGroupsRes.data || []);
setRequests(requestsRes.data || []);
} else {
// Fetch only joined groups and user requests for non-organizers
const [joinedGroupsRes, userRequestRes] = await Promise.all([
apiCall(GET_JOINED_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>,
apiCall(USER_REQUEST, "GET") as Promise<ApiResponse<UserJoinRequest[]>>,
]);
console.log("Joined Groups:", joinedGroupsRes.data);
console.log("User Requests:", userRequestRes.data);
setJoinedGroups(joinedGroupsRes.data || []);
setUserRequest(userRequestRes.data || []);
}
@@ -81,113 +86,64 @@ const ManagementScreen: FC = () => {
} finally {
setLoading(false);
}
}, [userRole, loading]);
// The only external dependency that should trigger a re-fetch is the user's role.
}, [user.role]); // **FIXED**: Removed `loading` from the dependency array
useFocusEffect(useCallback(() => { fetchData(); }, []));
// --- FIXED: useFocusEffect now has a stable dependency ---
useFocusEffect(
useCallback(() => {
fetchData();
}, [fetchData]) // This will now only re-run if fetchData (and thus user.role) changes.
);
// --- HANDLERS (No changes needed here) ---
const handleViewGroup = (item: JoinedGroup) => {
console.log("PPP",item)
navigation.navigate('Invite', { groupId: item.id } as any);
}
const handleAction = async (url: string, body: object, successMessage: string, failureMessage: string, action: string) => {
// ... (logic remains the same)
console.log(url)
// Assuming you have an 'Invite' screen in your RootTabParamList or another navigator
// navigation.navigate('Invite' as any, { groupId: item.id });
};
const handleAction = async (url: string, successMessage: string, failureMessage: string) => {
try {
setLoading(true); // Optionally show a loading state during the action
// Make the POST request with an empty body
// No need to set loading here if the whole screen is already loading
const res = await apiCall(url, "PUT", {}) as ApiResponse<null> | null;
if (res && res.statusCode === 200) {
// Use the success message from the API response
Alert.alert("Success", res.message || `Request ${action}d successfully.`);
fetchData(); // Refresh the list of requests
Alert.alert("Success", res.message || successMessage);
fetchData(); // Refresh data after action
} else {
throw new Error(res?.message || failureMessage);
}
} catch (err: any) {
Alert.alert("Error", err.message || "An unexpected error occurred.");
} finally {
setLoading(false);
}
};
const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL + req.requestId + "/approve", { requestId: req.requestId }, `${req.userName} has been added.`, "Failed to approve request.", "Approve");
const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL + req.requestId + "/reject?reason=/" + " ", { requestId: req.requestId }, `Request from ${req.userName} has been declined.`, "Failed to decline request.", "Decline");
const handleApprove = (req: JoinRequest) => handleAction(APPROVE_REQUEST_URL + req.requestId + "/approve", `${req.userName} has been added.`, "Failed to approve request.");
const handleDecline = (req: JoinRequest) => handleAction(DECLINE_REQUEST_URL + req.requestId + "/reject?reason=/", `Request from ${req.userName} has been declined.`, "Failed to decline request.");
const handleManageGroup = (group: MyGroup) => navigation.navigate('ManagementTabs', { groupId: group.id });
// --- RENDER LOGIC ---
const renderContent = () => {
if (error) {
return <Text style={styles.emptyText}>{error}</Text>;
if (error) return <Text style={styles.emptyText}>{error}</Text>;
// Improved loading logic to avoid hiding existing data during a refresh
if (loading && myGroups.length === 0 && joinedGroups.length === 0 && requests.length === 0) {
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
}
switch (activeTab) {
case "My Groups":
if (loading) {
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
}
return (
<FlatList
data={myGroups}
renderItem={({ item }: ListRenderItemInfo<MyGroup>) => <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} // This handles the "continuing loading" on pull-to-refresh
contentContainerStyle={{ paddingBottom: 100 }}
/>
);
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":
if (loading && joinedGroups.length === 0) {
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
}
return (
<FlatList
data={joinedGroups}
renderItem={({ item }: ListRenderItemInfo<JoinedGroup>) => <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} // This handles the "continuing loading" on pull-to-refresh
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":
if (loading && requests.length === 0) {
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
}
return (
<FlatList
data={requests}
renderItem={({ item }: ListRenderItemInfo<JoinRequest>) => <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} // This handles the "continuing loading" on pull-to-refresh
contentContainerStyle={{ paddingBottom: 100 }}
/>
);
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":
if (loading && requests.length === 0) {
return <ActivityIndicator size="large" color={colors.primary} style={styles.loader} />;
}
return (
<FlatList
data={userRequest}
renderItem={({ item }: ListRenderItemInfo<UserJoinRequest>) => <UserRequested item={item} />}
keyExtractor={(item) => item.requestId}
ListEmptyComponent={<Text style={styles.emptyText}>No pending join requests.</Text>}
onRefresh={fetchData}
refreshing={loading} // This handles the "continuing loading" on pull-to-refresh
contentContainerStyle={{ paddingBottom: 100 }}
/>
);
return <FlatList data={userRequest} renderItem={({ item }) => <UserRequested item={item} />} keyExtractor={(item) => item.requestId} ListEmptyComponent={<Text style={styles.emptyText}>You haven't sent any join requests.</Text>} onRefresh={fetchData} refreshing={loading} contentContainerStyle={{ paddingBottom: 100 }} />;
default: return null;
}
};
const tabs: ActiveTab[] = userRole.role === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"];
const tabs: ActiveTab[] = user.role === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"];
console.log("user request : ", userRequest)
return (
<AppView style={styles.container} paddingHorizontal={0}>
<View style={styles.header}>
@@ -201,32 +157,6 @@ const ManagementScreen: FC = () => {
))}
</View>
<View style={styles.contentArea}>
{
activeTab === "Requests" && userRole === "ORGANIZER" ? (
<TouchableOpacity
style={{
position: "absolute",
bottom: 85,
right: 20,
backgroundColor: colors.pastelOrange,
width: 60,
height: 60,
borderRadius: 30,
justifyContent: "center",
alignItems: "center",
shadowColor: "#000",
shadowOpacity: 0.2,
shadowRadius: 5,
shadowOffset: { width: 0, height: 3 },
elevation: 5, // for Android shadow
}}
onPress={() => navigation.navigate("YourContacts" as never)}
>
<Ionicons name="person-add" size={28} color={colors.textDark} />
</TouchableOpacity>
) : null
}
{renderContent()}
</View>
</AppView>
@@ -234,59 +164,17 @@ const ManagementScreen: FC = () => {
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f6fa'
},
header: {
paddingHorizontal: 20,
paddingBottom: 15,
},
title: {
fontSize: 22, fontWeight: "600", color: colors.Settings
},
segmentedControl: {
flexDirection: 'row',
backgroundColor: '#e9ecef',
borderRadius: 10,
marginHorizontal: 20,
marginBottom: 20,
padding: 3,
},
segmentButton: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
borderRadius: 8,
},
segmentActive: {
backgroundColor: colors.pastelOrange, // Example color
elevation: 3,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 5,
},
segmentText: {
fontSize: 14,
fontWeight: '600',
color: '#6c757d',
},
segmentTextActive: {
color: colors.textDark, // Example color
},
contentArea: {
flex: 1,
paddingHorizontal: 20,
},
loader: {
marginTop: 50,
},
emptyText: {
textAlign: 'center',
marginTop: 60,
fontSize: 16,
color: '#6c757d',
},
container: { flex: 1, backgroundColor: '#f5f6fa' },
header: { paddingHorizontal: 20, paddingBottom: 15, paddingTop: 10 },
title: { fontSize: 22, fontWeight: "600", color: colors.Settings },
segmentedControl: { flexDirection: 'row', backgroundColor: '#e9ecef', borderRadius: 10, marginHorizontal: 20, marginBottom: 20, padding: 3 },
segmentButton: { flex: 1, paddingVertical: 12, alignItems: 'center', borderRadius: 8 },
segmentActive: { backgroundColor: colors.pastelOrange, elevation: 3, shadowColor: '#000', shadowOpacity: 0.1, shadowRadius: 5 },
segmentText: { fontSize: 14, fontWeight: '600', color: '#6c757d' },
segmentTextActive: { color: colors.textDark },
contentArea: { flex: 1, paddingHorizontal: 20 },
loader: { marginTop: 50 },
emptyText: { textAlign: 'center', marginTop: 60, fontSize: 16, color: '#6c757d' },
});
export default ManagementScreen;