mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
Enhance navigation and data handling: update BottomNavbar to pass selected department, refactor GameSettingsScreen and PlayersScreen for improved subgroup management, and adjust API calls to support department-specific data.
This commit is contained in:
1 parent
43c8bb2bff
commit
4a0468d155
7 files changed
+147
-59
No files matched your search
@@ -3,17 +3,29 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import {clearGameSession, getSession} from '../../hooks/SessionStore';
|
||||
import API, {API_BASE_URL} from '../API/API';
|
||||
|
||||
const BottomNavbar = ({navigation, routeName}) => {
|
||||
const BottomNavbar = ({navigation, routeName, routeParams}) => {
|
||||
// Initialisation ------------
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
const session = getSession();
|
||||
|
||||
const selectedDepartment = routeName === 'DepartmentSettingsScreen' && routeParams?.SGid
|
||||
? {SGid: routeParams.SGid, SubGroupName: routeParams.SubGroupName || routeParams.selectedDepartmentName || 'Department'}
|
||||
: null;
|
||||
|
||||
const adminTabs = [
|
||||
{label: 'Settings', route: 'GameSettingsScreen', onPress: () => navigation.navigate('GameSettingsScreen')},
|
||||
{label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')},
|
||||
{label: 'Players', route: 'PlayersScreen', onPress: () => navigation.navigate('PlayersScreen')},
|
||||
{label: 'Leaderboard', route: 'LeaderboardScreen', onPress: () => navigation.navigate('LeaderboardScreen')},
|
||||
{
|
||||
label: 'Players',
|
||||
route: 'PlayersScreen',
|
||||
onPress: () => navigation.navigate('PlayersScreen', selectedDepartment ? {department: selectedDepartment} : undefined),
|
||||
},
|
||||
{
|
||||
label: 'Leaderboard',
|
||||
route: 'LeaderboardScreen',
|
||||
onPress: () => navigation.navigate('LeaderboardScreen', selectedDepartment ? {department: selectedDepartment} : undefined),
|
||||
},
|
||||
];
|
||||
|
||||
// Only show the Leave tab when the player is actually in a game
|
||||
|
||||
@@ -35,7 +35,7 @@ const Screen = ({ children, style, showBack = false }) => {
|
||||
<View style={[styles.screen, isAuthScreen && {paddingTop: insets.top, paddingBottom: insets.bottom}]}>
|
||||
{!isAuthScreen && <TopNavbar title={pageTitle} showBack={showBack} navigation={navigation} />}
|
||||
<View style={[styles.content, style]}>{children}</View>
|
||||
{!isAuthScreen && <BottomNavbar navigation={navigation} routeName={routeName} />}
|
||||
{!isAuthScreen && <BottomNavbar navigation={navigation} routeName={routeName} routeParams={route.params} />}
|
||||
<StatusBar style="light" />
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useCallback, useState} from 'react';
|
||||
import {ActivityIndicator, Alert, ScrollView, StyleSheet, Switch, Text, TextInput, View} from 'react-native';
|
||||
import {useFocusEffect} from '@react-navigation/native';
|
||||
import Screen from '../layout/Screen';
|
||||
import Card from '../UI/Card';
|
||||
import {Button, ButtonTray} from '../UI/Button';
|
||||
@@ -11,7 +12,6 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
|
||||
const session = getSession();
|
||||
const {getLobby, updateGroup, getSubgroups, createSubgroup, deleteSubgroup, resetGame, getAdminWaitlist, approveAdmin, rejectAdmin, getUser, disbandTeams} = useGameHook();
|
||||
const isBusiness = Boolean(session.isBusiness);
|
||||
|
||||
// State ----------------------
|
||||
|
||||
@@ -27,15 +27,16 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
const [memberJoinCode, setMemberJoinCode] = useState('');
|
||||
const [waitlist, setWaitlist] = useState([]);
|
||||
const [waitlistNames, setWaitlistNames] = useState({});
|
||||
const [isBusinessGroup, setIsBusinessGroup] = useState(Boolean(session.isBusiness));
|
||||
// Business subgroup management
|
||||
const [subgroupList, setSubgroupList] = useState([]);
|
||||
const [newDeptName, setNewDeptName] = useState('');
|
||||
|
||||
// Handlers -------------------
|
||||
|
||||
const loadWaitlist = async () => {
|
||||
if (!session.currentGid) return;
|
||||
const rows = await getAdminWaitlist(session.currentGid);
|
||||
const loadWaitlist = useCallback(async (gid) => {
|
||||
if (!gid) return;
|
||||
const rows = await getAdminWaitlist(gid);
|
||||
setWaitlist(rows || []);
|
||||
const names = {};
|
||||
for (const entry of (rows || [])) {
|
||||
@@ -43,27 +44,28 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
if (u) names[entry.Uid] = u.username;
|
||||
}
|
||||
setWaitlistNames(names);
|
||||
};
|
||||
}, [getAdminWaitlist, getUser]);
|
||||
|
||||
const loadSubgroups = async () => {
|
||||
if (!session.currentGid) return;
|
||||
const sgs = await getSubgroups(session.currentGid);
|
||||
const loadSubgroups = useCallback(async (gid) => {
|
||||
if (!gid) return;
|
||||
const sgs = await getSubgroups(gid);
|
||||
setSubgroupList(sgs || []);
|
||||
// Set the default member join code from first non-admin subgroup
|
||||
const memberSg = (sgs || []).find((sg) => !sg.IsAdminGroup);
|
||||
if (memberSg) setMemberJoinCode(memberSg.JoinCode || '');
|
||||
};
|
||||
}, [getSubgroups]);
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
if (!session.currentGid) {
|
||||
const loadSettings = useCallback(async () => {
|
||||
const activeSession = getSession();
|
||||
if (!activeSession.currentGid) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const group = await getLobby(session.currentGid);
|
||||
const group = await getLobby(activeSession.currentGid);
|
||||
if (group) {
|
||||
setGroupName(group.GroupName || '');
|
||||
setBusinessName(group.BusinessOrSchoolName || '');
|
||||
setIsBusinessGroup(Boolean(group.BusinessOrSchoolName || group.OrgJoinCode));
|
||||
const serverTeams = Boolean(group.TeamsEnabled);
|
||||
setTeamsEnabled(serverTeams);
|
||||
setServerTeamsEnabled(serverTeams);
|
||||
@@ -72,12 +74,17 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
setAdminJoinCode(group.AdminJoinCode || '');
|
||||
setOrgJoinCode(group.OrgJoinCode || '');
|
||||
}
|
||||
await loadSubgroups();
|
||||
await loadWaitlist();
|
||||
await loadSubgroups(activeSession.currentGid);
|
||||
await loadWaitlist(activeSession.currentGid);
|
||||
setLoading(false);
|
||||
};
|
||||
load();
|
||||
}, []);
|
||||
}, [getLobby, loadSubgroups, loadWaitlist]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setLoading(true);
|
||||
loadSettings();
|
||||
}, [loadSettings]),
|
||||
);
|
||||
|
||||
// --- Individual save ---
|
||||
const saveSettings = async () => {
|
||||
@@ -100,7 +107,7 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
const handleSave = async () => {
|
||||
if (!session.currentGid) return;
|
||||
// Business save — only save business name
|
||||
if (isBusiness) {
|
||||
if (isBusinessGroup) {
|
||||
setSaving(true);
|
||||
await updateGroup(session.currentGid, {
|
||||
BusinessOrSchoolName: businessName.trim(),
|
||||
@@ -180,7 +187,11 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
|
||||
// Business — navigate to department settings
|
||||
const handleEnterDepartment = (sg) => {
|
||||
navigation.navigate('DepartmentSettingsScreen', {SGid: sg.SGid, Gid: session.currentGid});
|
||||
navigation.navigate('DepartmentSettingsScreen', {
|
||||
SGid: sg.SGid,
|
||||
Gid: session.currentGid,
|
||||
SubGroupName: sg.SubGroupName,
|
||||
});
|
||||
};
|
||||
|
||||
// View -----------------------
|
||||
@@ -205,7 +216,7 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
const departments = subgroupList.filter((sg) => !sg.IsAdminGroup);
|
||||
|
||||
// ===== Business Admin View =====
|
||||
if (isBusiness) {
|
||||
if (isBusinessGroup) {
|
||||
return (
|
||||
<Screen>
|
||||
<ScrollView contentContainerStyle={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||
|
||||
@@ -13,7 +13,7 @@ const LeaderboardScreen = ({navigation, route}) => {
|
||||
const isAdmin = session.isAcceptedAdmin;
|
||||
const isBusiness = Boolean(session.isBusiness);
|
||||
const selectedDepartment = route?.params?.department || null;
|
||||
const {getLobby, getTeams, getTeamMembers, getGroupMembers, getCaches, getUser, deleteTeam, resetPlayerProgress, resetTeamProgress, getSubgroups} = useGameHook();
|
||||
const {getLobby, getSubgroup, getTeams, getTeamMembers, getGroupMembers, getCaches, getUser, deleteTeam, resetPlayerProgress, resetTeamProgress, getSubgroups} = useGameHook();
|
||||
|
||||
// State ----------------------
|
||||
|
||||
@@ -33,7 +33,16 @@ const LeaderboardScreen = ({navigation, route}) => {
|
||||
if (!session.currentGid) { setLoading(false); return; }
|
||||
|
||||
const group = await getLobby(session.currentGid);
|
||||
const isTeams = Boolean(group?.TeamsEnabled);
|
||||
|
||||
const effectiveSGid = isBusiness
|
||||
? ((isAdmin && selectedDepartment?.SGid) ? selectedDepartment.SGid : (isAdmin ? null : session.currentSGid))
|
||||
: null;
|
||||
|
||||
let isTeams = Boolean(group?.TeamsEnabled);
|
||||
if (effectiveSGid !== null && effectiveSGid !== undefined) {
|
||||
const subgroup = await getSubgroup(effectiveSGid);
|
||||
if (subgroup) isTeams = Boolean(subgroup.TeamsEnabled);
|
||||
}
|
||||
setTeamsEnabled(isTeams);
|
||||
|
||||
if (isBusiness && isAdmin && !selectedDepartment) {
|
||||
@@ -43,24 +52,16 @@ const LeaderboardScreen = ({navigation, route}) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const leaderboardSGid = (isBusiness && isAdmin && selectedDepartment?.SGid)
|
||||
? selectedDepartment.SGid
|
||||
: null;
|
||||
|
||||
const caches = await getCaches(session.currentGid, leaderboardSGid);
|
||||
const caches = await getCaches(session.currentGid, effectiveSGid ?? null);
|
||||
const total = (caches || []).length;
|
||||
setTotalCacheCount(total);
|
||||
|
||||
if (isTeams) {
|
||||
// Build team rankings
|
||||
const teams = await getTeams(session.currentGid);
|
||||
const teams = await getTeams(session.currentGid, effectiveSGid ?? null);
|
||||
const teamData = [];
|
||||
|
||||
const scopedTeams = leaderboardSGid
|
||||
? (teams || []).filter((team) => !team.SGid || team.SGid === leaderboardSGid)
|
||||
: (teams || []);
|
||||
|
||||
for (const team of scopedTeams) {
|
||||
for (const team of (teams || [])) {
|
||||
const members = await getTeamMembers(team.Tid);
|
||||
const memberData = [];
|
||||
for (const m of (members || [])) {
|
||||
@@ -98,12 +99,9 @@ const LeaderboardScreen = ({navigation, route}) => {
|
||||
}
|
||||
} else {
|
||||
// Build player rankings (no teams)
|
||||
const allMembers = await getGroupMembers(session.currentGid);
|
||||
const scopedMembers = leaderboardSGid
|
||||
? (allMembers || []).filter((mem) => mem.SGid === leaderboardSGid)
|
||||
: (allMembers || []);
|
||||
const scopedMembers = await getGroupMembers(session.currentGid, effectiveSGid ?? null);
|
||||
const playerData = [];
|
||||
for (const m of scopedMembers.filter((mem) => !mem.IsAcceptedAdmin)) {
|
||||
for (const m of (scopedMembers || []).filter((mem) => !mem.IsAcceptedAdmin)) {
|
||||
const user = await getUser(m.Uid);
|
||||
const count = (caches || []).filter((c) =>
|
||||
(c.Claims || []).some((cl) => cl.Uid === m.Uid)
|
||||
@@ -119,7 +117,7 @@ const LeaderboardScreen = ({navigation, route}) => {
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}, [session.currentGid, session.currentTid, isBusiness, isAdmin, selectedDepartment, selectedDepartment?.SGid]);
|
||||
}, [session.currentGid, session.currentTid, session.currentSGid, isBusiness, isAdmin, selectedDepartment, selectedDepartment?.SGid]);
|
||||
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
|
||||
|
||||
@@ -1,28 +1,43 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {ActivityIndicator, Alert, ScrollView, StyleSheet, Text, View} from 'react-native';
|
||||
import {ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, View} from 'react-native';
|
||||
import Screen from '../layout/Screen';
|
||||
import Card from '../UI/Card';
|
||||
import {Button} from '../UI/Button';
|
||||
import useGameHook from '../../hooks/useGameHook';
|
||||
import {getSession} from '../../hooks/SessionStore';
|
||||
|
||||
const PlayersScreen = () => {
|
||||
const PlayersScreen = ({navigation, route}) => {
|
||||
// Initialisation ------------
|
||||
|
||||
const session = getSession();
|
||||
const {getTeams, getTeamMembers, getGroupMembers, removeMember, resetPlayerProgress, getUser, getAdminWaitlist, approveAdmin, rejectAdmin, getSubgroups} = useGameHook();
|
||||
const isBusiness = Boolean(session.isBusiness);
|
||||
const isAdmin = Boolean(session.isAcceptedAdmin);
|
||||
const selectedDepartment = route?.params?.department || null;
|
||||
|
||||
// State ----------------------
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [players, setPlayers] = useState([]);
|
||||
const [departmentList, setDepartmentList] = useState([]);
|
||||
|
||||
// Handlers -------------------
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
if (!session.currentGid) { setLoading(false); return; }
|
||||
|
||||
const effectiveSGid = isBusiness
|
||||
? ((isAdmin && selectedDepartment?.SGid) ? selectedDepartment.SGid : (isAdmin ? null : session.currentSGid))
|
||||
: null;
|
||||
|
||||
if (isBusiness && isAdmin && !selectedDepartment) {
|
||||
const sgs = await getSubgroups(session.currentGid);
|
||||
setDepartmentList((sgs || []).filter((sg) => !sg.IsAdminGroup));
|
||||
setPlayers([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load subgroup names for department display (business accounts)
|
||||
let subgroupNameMap = {};
|
||||
if (isBusiness) {
|
||||
@@ -33,10 +48,10 @@ const PlayersScreen = () => {
|
||||
}
|
||||
|
||||
// Get ALL members (including admins)
|
||||
const allMembers = await getGroupMembers(session.currentGid);
|
||||
const allMembers = await getGroupMembers(session.currentGid, effectiveSGid ?? null);
|
||||
|
||||
// Get admin waitlist entries
|
||||
const waitlist = await getAdminWaitlist(session.currentGid);
|
||||
const waitlist = effectiveSGid ? [] : await getAdminWaitlist(session.currentGid);
|
||||
|
||||
// Deduplicate allMembers by Uid — a user may have two rows if they were a
|
||||
// regular member before being approved as an admin. Prefer the admin row.
|
||||
@@ -46,7 +61,7 @@ const PlayersScreen = () => {
|
||||
.filter((m) => { if (seenUids.has(m.Uid)) return false; seenUids.add(m.Uid); return true; });
|
||||
|
||||
// Build team lookup: { Uid -> { teamCode, teamName, isLeader } }
|
||||
const allTeams = await getTeams(session.currentGid);
|
||||
const allTeams = await getTeams(session.currentGid, effectiveSGid ?? null);
|
||||
const teamInfoByUid = {};
|
||||
for (const team of (allTeams || [])) {
|
||||
const tms = await getTeamMembers(team.Tid);
|
||||
@@ -107,7 +122,7 @@ const PlayersScreen = () => {
|
||||
|
||||
setPlayers(playerList);
|
||||
setLoading(false);
|
||||
}, [session.currentGid]);
|
||||
}, [session.currentGid, session.currentUid, session.currentSGid, isBusiness, isAdmin, selectedDepartment, selectedDepartment?.SGid]);
|
||||
|
||||
useEffect(() => { loadData(); }, [loadData]);
|
||||
|
||||
@@ -164,8 +179,34 @@ const PlayersScreen = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isBusiness && isAdmin && !selectedDepartment) {
|
||||
return (
|
||||
<Screen style={styles.container}>
|
||||
<Text style={styles.sectionTitle}>Departments</Text>
|
||||
<ScrollView style={styles.listSection}>
|
||||
{departmentList.map((department) => (
|
||||
<Pressable
|
||||
key={department.SGid}
|
||||
onPress={() => navigation.navigate('PlayersScreen', {department})}
|
||||
>
|
||||
<Card>
|
||||
<View style={styles.playerRow}>
|
||||
<Text style={styles.playerName}>{department.SubGroupName}</Text>
|
||||
<Text style={styles.openLabel}>Open</Text>
|
||||
</View>
|
||||
</Card>
|
||||
</Pressable>
|
||||
))}
|
||||
{departmentList.length === 0 && (
|
||||
<Text style={styles.emptyText}>No departments yet.</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Screen style={styles.container} showBack={Boolean(selectedDepartment)}>
|
||||
<Text style={styles.sectionTitle}>Players</Text>
|
||||
<ScrollView style={styles.listSection}>
|
||||
{players.map((player) => (
|
||||
@@ -255,6 +296,7 @@ const styles = StyleSheet.create({
|
||||
paddingHorizontal: 15,
|
||||
paddingTop: 15,
|
||||
},
|
||||
openLabel: {fontSize: 13, fontWeight: '700', color: '#2563eb'},
|
||||
listSection: {flex: 1, paddingHorizontal: 15},
|
||||
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14},
|
||||
playerRow: {
|
||||
|
||||
@@ -11,7 +11,8 @@ const TeamScreen = () => {
|
||||
|
||||
const session = getSession();
|
||||
const isAdmin = session.isAcceptedAdmin;
|
||||
const {getLobby, getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook();
|
||||
const isBusiness = Boolean(session.isBusiness);
|
||||
const {getLobby, getSubgroup, getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook();
|
||||
|
||||
// State ----------------------
|
||||
|
||||
@@ -46,11 +47,19 @@ const TeamScreen = () => {
|
||||
if (!session.currentGid) { setLoading(false); return; }
|
||||
|
||||
const group = await getLobby(session.currentGid);
|
||||
if (group && !group.TeamsEnabled) {
|
||||
let teamsAreEnabled = Boolean(group?.TeamsEnabled);
|
||||
|
||||
if (isBusiness && session.currentSGid) {
|
||||
const subgroup = await getSubgroup(session.currentSGid);
|
||||
if (subgroup) teamsAreEnabled = Boolean(subgroup.TeamsEnabled);
|
||||
}
|
||||
|
||||
if (!teamsAreEnabled) {
|
||||
setTeamsDisabled(true);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setTeamsDisabled(false);
|
||||
|
||||
if (!activeTid) {
|
||||
const waitlist = await getAdminWaitlist(session.currentGid, session.currentUid);
|
||||
@@ -70,7 +79,11 @@ const TeamScreen = () => {
|
||||
if (!teamCode.trim()) return;
|
||||
// Force uppercase just in case a lowercase code is pasted
|
||||
const code = teamCode.trim().toUpperCase();
|
||||
const result = await joinTeamByCode({JoinCode: code, Uid: session.currentUid});
|
||||
const result = await joinTeamByCode({
|
||||
JoinCode: code,
|
||||
Uid: session.currentUid,
|
||||
ExpectedSGid: session.currentSGid,
|
||||
});
|
||||
if (!result) return;
|
||||
|
||||
// If the server indicates this code matched the admin join code
|
||||
|
||||
@@ -89,8 +89,10 @@ const useGameHook = () => {
|
||||
return response.isSuccess ? response.result : null;
|
||||
};
|
||||
|
||||
const getGroupMembers = async (gid) => {
|
||||
const response = await API.get(`${subgroupMembersEndpoint}?Gid=${gid}`);
|
||||
const getGroupMembers = async (gid, sgid = null) => {
|
||||
let url = `${subgroupMembersEndpoint}?Gid=${gid}`;
|
||||
if (sgid !== null && sgid !== undefined) url += `&SGid=${sgid}`;
|
||||
const response = await API.get(url);
|
||||
return response.isSuccess ? response.result : [];
|
||||
};
|
||||
|
||||
@@ -100,8 +102,10 @@ const useGameHook = () => {
|
||||
};
|
||||
|
||||
// Teams
|
||||
const getTeams = async (gid) => {
|
||||
const response = await API.get(`${teamsEndpoint}?Gid=${gid}`);
|
||||
const getTeams = async (gid, sgid = null) => {
|
||||
let url = `${teamsEndpoint}?Gid=${gid}`;
|
||||
if (sgid !== null && sgid !== undefined) url += `&SGid=${sgid}`;
|
||||
const response = await API.get(url);
|
||||
return response.isSuccess ? response.result : [];
|
||||
};
|
||||
|
||||
@@ -131,7 +135,15 @@ const useGameHook = () => {
|
||||
};
|
||||
|
||||
const joinTeamByCode = async (payload) => {
|
||||
const response = await API.post(teamMembersEndpoint, payload);
|
||||
const data = {
|
||||
Uid: payload.Uid,
|
||||
};
|
||||
if (payload.JoinCode !== undefined) data.JoinCode = payload.JoinCode;
|
||||
if (payload.Tid !== undefined) data.Tid = payload.Tid;
|
||||
if (payload.ExpectedSGid !== undefined && payload.ExpectedSGid !== null) {
|
||||
data.ExpectedSGid = payload.ExpectedSGid;
|
||||
}
|
||||
const response = await API.post(teamMembersEndpoint, data);
|
||||
return response.isSuccess ? response.result : null;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user