308 lines
11 KiB
TypeScript
308 lines
11 KiB
TypeScript
// src/screens/ManagementScreen.tsx
|
|
|
|
import React, { useState, useCallback, FC } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
FlatList,
|
|
TouchableOpacity,
|
|
ActivityIndicator,
|
|
Alert,
|
|
ListRenderItemInfo,
|
|
} from "react-native";
|
|
import { useNavigation, useFocusEffect } 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 } 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 } from '../../types';
|
|
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";
|
|
|
|
type GroupManagementNavProp = StackNavigationProp<RootStackParamList>;
|
|
type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
|
|
|
|
const ManagementScreen: FC = () => {
|
|
const user = useSelector((state:any) => state.user)
|
|
const [activeTab, setActiveTab] = useState<ActiveTab>("My Groups");
|
|
const navigation = useNavigation<GroupManagementNavProp>();
|
|
const [myGroups, setMyGroups] = useState<MyGroup[]>([]);
|
|
const [joinedGroups, setJoinedGroups] = useState<JoinedGroup[]>([]);
|
|
const [requests, setRequests] = useState<JoinRequest[]>([]);
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [userRole,setUserRole] = useState<string | null>(null)
|
|
const userStatus = async () => {
|
|
try {
|
|
const credentials = await Keychain.getGenericPassword();
|
|
if (!credentials) {
|
|
Alert.alert("Error", "You are not logged in");
|
|
return;
|
|
}
|
|
const token = credentials.password;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
const response = await fetch(`${BASE_URL}user/profile/status`, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "*/*",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
});
|
|
|
|
const json = await response.json();
|
|
|
|
console.log("👤 Profile Status Response:", json.data.role);
|
|
if (json.status === "success") {
|
|
setUserRole(json.data.role);
|
|
|
|
if (json.data.profileStatus === false) {
|
|
|
|
}
|
|
} else {
|
|
setError(json.message || "Failed to fetch status");
|
|
}
|
|
} catch (err) {
|
|
setError("Something went wrong. Please try again.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useFocusEffect(
|
|
useCallback(() => {
|
|
userStatus();
|
|
}, [])
|
|
);
|
|
|
|
const fetchData = useCallback(async () => {
|
|
if (loading) return;
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
console.log(" abin user : ", userRole)
|
|
if (userRole === "ORGANIZER") {
|
|
// Fetch all data concurrently
|
|
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[]>>, // Assuming this is also needed
|
|
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 for non-organizers
|
|
const [joinedGroupsRes] = await Promise.all([
|
|
apiCall(GET_JOINED_GROUP_URL, "GET") as Promise<ApiResponse<JoinedGroup[]>>, // Assuming this is also needed
|
|
]);
|
|
setJoinedGroups(joinedGroupsRes.data || []);
|
|
}
|
|
} catch (e) {
|
|
console.error("Fetch Data Error:", e);
|
|
setError("Failed to fetch data. Please pull to refresh.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [userRole, loading]);
|
|
|
|
useFocusEffect(useCallback(() => { fetchData(); }, []));
|
|
|
|
const handleAction = async (url: string, body: object, successMessage: string, failureMessage: string, action: string) => {
|
|
// ... (logic remains the same)
|
|
console.log(url)
|
|
try {
|
|
setLoading(true); // Optionally show a loading state during the action
|
|
// Make the POST request with an empty body
|
|
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
|
|
} 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 handleManageGroup = (group: MyGroup) => navigation.navigate('EditGroup', { groupId: group.id });
|
|
|
|
const renderContent = () => {
|
|
if (error) {
|
|
return <Text style={styles.emptyText}>{error}</Text>;
|
|
}
|
|
|
|
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 }}
|
|
/>
|
|
);
|
|
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} />}
|
|
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 }}
|
|
/>
|
|
);
|
|
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 }}
|
|
/>
|
|
);
|
|
default: return null;
|
|
}
|
|
};
|
|
|
|
const tabs: ActiveTab[] = userRole === 'ORGANIZER' ? ["My Groups", "Joined Groups", "Requests"] : ["Joined Groups", "Your Requests"];
|
|
|
|
return (
|
|
<AppView style={styles.container} paddingHorizontal={0}>
|
|
<View style={styles.header}>
|
|
<Text style={styles.title}>Group Management</Text>
|
|
</View>
|
|
<View style={styles.segmentedControl}>
|
|
{tabs.map((tab) => (
|
|
<TouchableOpacity key={tab} style={[styles.segmentButton, activeTab === tab && styles.segmentActive]} onPress={() => setActiveTab(tab)}>
|
|
<Text style={[styles.segmentText, activeTab === tab && styles.segmentTextActive]}>{tab}</Text>
|
|
</TouchableOpacity>
|
|
))}
|
|
</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>
|
|
);
|
|
};
|
|
|
|
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',
|
|
},
|
|
});
|
|
|
|
export default ManagementScreen;
|