mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
Enhance department management: add department name display in cache cards, update navigation to include selected department, and improve department selection in map screen.
This commit is contained in:
1 parent
dd5dd83737
commit
43c8bb2bff
5 files changed
+121
-9
No files matched your search
@@ -2,7 +2,7 @@ import {StyleSheet, Text, View} from 'react-native';
|
|||||||
import Card from '../UI/Card';
|
import Card from '../UI/Card';
|
||||||
import {Button} from '../UI/Button';
|
import {Button} from '../UI/Button';
|
||||||
|
|
||||||
const CacheCardItem = ({cache, isAdmin, isClaimed, isSelected, onEdit, onDelete, onSelect, disabled}) => {
|
const CacheCardItem = ({cache, isAdmin, isClaimed, isSelected, onEdit, onDelete, onSelect, disabled, departmentName}) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
|
|
||||||
const displayName = cache.name || cache.clue || 'Unnamed Cache';
|
const displayName = cache.name || cache.clue || 'Unnamed Cache';
|
||||||
@@ -16,6 +16,9 @@ const CacheCardItem = ({cache, isAdmin, isClaimed, isSelected, onEdit, onDelete,
|
|||||||
<View style={styles.topRow}>
|
<View style={styles.topRow}>
|
||||||
<View style={styles.nameWrap}>
|
<View style={styles.nameWrap}>
|
||||||
<Text style={[styles.name, isClaimed && styles.claimedText, disabled && styles.disabledText]} numberOfLines={1}>{displayName}</Text>
|
<Text style={[styles.name, isClaimed && styles.claimedText, disabled && styles.disabledText]} numberOfLines={1}>{displayName}</Text>
|
||||||
|
{isAdmin && departmentName ? (
|
||||||
|
<Text style={styles.departmentText} numberOfLines={1}>Department: {departmentName}</Text>
|
||||||
|
) : null}
|
||||||
{cache.name ? (
|
{cache.name ? (
|
||||||
<Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text>
|
<Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -87,6 +90,7 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
clue: {fontSize: 13, color: '#6b7280', marginTop: 2},
|
clue: {fontSize: 13, color: '#6b7280', marginTop: 2},
|
||||||
|
departmentText: {fontSize: 13, color: '#2563eb', marginTop: 2, fontWeight: '600'},
|
||||||
nameWrap: {flex: 1, marginRight: 8},
|
nameWrap: {flex: 1, marginRight: 8},
|
||||||
name: {fontSize: 15, fontWeight: '600', color: '#1f2937'},
|
name: {fontSize: 15, fontWeight: '600', color: '#1f2937'},
|
||||||
claimedText: {color: '#9ca3af'},
|
claimedText: {color: '#9ca3af'},
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ const DepartmentSettingsScreen = ({route, navigation}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleManageCaches = () => {
|
const handleManageCaches = () => {
|
||||||
navigation.navigate('MapScreen');
|
navigation.navigate('MapScreen', {
|
||||||
|
selectedDepartmentSGid: SGid,
|
||||||
|
selectedDepartmentName: deptName || null,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ const GameSettingsScreen = ({navigation}) => {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.deptCardActions}>
|
<View style={styles.deptCardActions}>
|
||||||
<Button
|
<Button
|
||||||
label="Open Department Game Settings"
|
label="Department Game"
|
||||||
onClick={() => handleEnterDepartment(sg)}
|
onClick={() => handleEnterDepartment(sg)}
|
||||||
styleButton={styles.deptOpenButton}
|
styleButton={styles.deptOpenButton}
|
||||||
styleLabel={styles.deptBtnLabel}
|
styleLabel={styles.deptBtnLabel}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {getFovCone} from '../../utils/geoMath';
|
|||||||
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05};
|
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05};
|
||||||
|
|
||||||
|
|
||||||
const MapScreen = ({navigation}) => {
|
const MapScreen = ({navigation, route}) => {
|
||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
@@ -25,6 +25,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
// Stable ref to prevent infinite re-render loops
|
// Stable ref to prevent infinite re-render loops
|
||||||
const getCachesRef = useRef(getCaches);
|
const getCachesRef = useRef(getCaches);
|
||||||
getCachesRef.current = getCaches;
|
getCachesRef.current = getCaches;
|
||||||
|
const lastAppliedRouteDepartmentRef = useRef(null);
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ const MapScreen = ({navigation}) => {
|
|||||||
const [newCoord, setNewCoord] = useState(null);
|
const [newCoord, setNewCoord] = useState(null);
|
||||||
const [newName, setNewName] = useState('');
|
const [newName, setNewName] = useState('');
|
||||||
const [newClue, setNewClue] = useState('');
|
const [newClue, setNewClue] = useState('');
|
||||||
|
const [selectedCacheSubgroupId, setSelectedCacheSubgroupId] = useState(null);
|
||||||
|
const [departmentDropdownOpen, setDepartmentDropdownOpen] = useState(false);
|
||||||
|
|
||||||
const claimDistance = groupInfo?.CacheTriggerMeters || 20;
|
const claimDistance = groupInfo?.CacheTriggerMeters || 20;
|
||||||
const isPlayer = inGame && !isAdmin;
|
const isPlayer = inGame && !isAdmin;
|
||||||
@@ -93,7 +96,36 @@ const MapScreen = ({navigation}) => {
|
|||||||
}, [inGame]);
|
}, [inGame]);
|
||||||
|
|
||||||
// Default member subgroup SGid for cache creation (first non-admin subgroup)
|
// Default member subgroup SGid for cache creation (first non-admin subgroup)
|
||||||
const defaultMemberSGid = subgroups.find((sg) => !sg.IsAdminGroup)?.SGid || null;
|
const departments = useMemo(
|
||||||
|
() => subgroups.filter((sg) => !sg.IsAdminGroup),
|
||||||
|
[subgroups],
|
||||||
|
);
|
||||||
|
const defaultMemberSGid = departments[0]?.SGid || null;
|
||||||
|
const departmentNamesById = useMemo(() => {
|
||||||
|
const map = {};
|
||||||
|
for (const subgroup of departments) {
|
||||||
|
map[String(subgroup.SGid)] = subgroup.SubGroupName || `Department ${subgroup.SGid}`;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [departments]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAdmin || departments.length === 0) return;
|
||||||
|
const routeSelectedSgid = route?.params?.selectedDepartmentSGid;
|
||||||
|
const hasRouteDepartment = departments.some((sg) => String(sg.SGid) === String(routeSelectedSgid));
|
||||||
|
const shouldApplyRouteDepartment =
|
||||||
|
hasRouteDepartment && String(lastAppliedRouteDepartmentRef.current) !== String(routeSelectedSgid);
|
||||||
|
if (shouldApplyRouteDepartment) {
|
||||||
|
setSelectedCacheSubgroupId(routeSelectedSgid);
|
||||||
|
lastAppliedRouteDepartmentRef.current = routeSelectedSgid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentStillValid = departments.some((sg) => String(sg.SGid) === String(selectedCacheSubgroupId));
|
||||||
|
if (!currentStillValid) {
|
||||||
|
setSelectedCacheSubgroupId(defaultMemberSGid);
|
||||||
|
}
|
||||||
|
}, [route?.params?.selectedDepartmentSGid, departments, isAdmin, selectedCacheSubgroupId, defaultMemberSGid]);
|
||||||
|
|
||||||
const loadCaches = useCallback(async () => {
|
const loadCaches = useCallback(async () => {
|
||||||
const currentGid = getSession().currentGid;
|
const currentGid = getSession().currentGid;
|
||||||
@@ -234,7 +266,11 @@ const MapScreen = ({navigation}) => {
|
|||||||
const handleJoinDepartment = async () => {
|
const handleJoinDepartment = async () => {
|
||||||
setJoinError('');
|
setJoinError('');
|
||||||
if (!deptCode.trim()) return;
|
if (!deptCode.trim()) return;
|
||||||
const result = await joinPrivateGame({JoinCode: deptCode.trim().toUpperCase(), Uid: session.currentUid});
|
const result = await joinPrivateGame({
|
||||||
|
JoinCode: deptCode.trim().toUpperCase(),
|
||||||
|
Uid: session.currentUid,
|
||||||
|
ExpectedGid: matchedGroup.Gid,
|
||||||
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
setJoinError('Invalid department code. Check the code and try again.');
|
setJoinError('Invalid department code. Check the code and try again.');
|
||||||
return;
|
return;
|
||||||
@@ -273,6 +309,8 @@ const MapScreen = ({navigation}) => {
|
|||||||
setNewCoord(fallback);
|
setNewCoord(fallback);
|
||||||
setNewName('');
|
setNewName('');
|
||||||
setNewClue('');
|
setNewClue('');
|
||||||
|
if (!selectedCacheSubgroupId && defaultMemberSGid) setSelectedCacheSubgroupId(defaultMemberSGid);
|
||||||
|
setDepartmentDropdownOpen(false);
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,6 +319,8 @@ const MapScreen = ({navigation}) => {
|
|||||||
setNewCoord(cache.coordinates);
|
setNewCoord(cache.coordinates);
|
||||||
setNewName(cache.name || '');
|
setNewName(cache.name || '');
|
||||||
setNewClue(cache.clue || '');
|
setNewClue(cache.clue || '');
|
||||||
|
setSelectedCacheSubgroupId(cache.subgroupId || defaultMemberSGid);
|
||||||
|
setDepartmentDropdownOpen(false);
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -292,24 +332,28 @@ const MapScreen = ({navigation}) => {
|
|||||||
|
|
||||||
const handleSaveCache = async () => {
|
const handleSaveCache = async () => {
|
||||||
if (!newCoord || !newClue.trim() || !session.currentGid) return;
|
if (!newCoord || !newClue.trim() || !session.currentGid) return;
|
||||||
|
const subgroupId = selectedCacheSubgroupId || defaultMemberSGid;
|
||||||
|
if (!subgroupId) return;
|
||||||
const payload = {
|
const payload = {
|
||||||
gid: session.currentGid,
|
gid: session.currentGid,
|
||||||
name: newName.trim(),
|
name: newName.trim(),
|
||||||
latitude: newCoord.latitude,
|
latitude: newCoord.latitude,
|
||||||
longitude: newCoord.longitude,
|
longitude: newCoord.longitude,
|
||||||
clue: newClue.trim(),
|
clue: newClue.trim(),
|
||||||
subgroupId: defaultMemberSGid,
|
subgroupId,
|
||||||
};
|
};
|
||||||
if (editingCacheId) payload.cacheId = editingCacheId;
|
if (editingCacheId) payload.cacheId = editingCacheId;
|
||||||
await upsertCache(payload);
|
await upsertCache(payload);
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setEditingCacheId(null);
|
setEditingCacheId(null);
|
||||||
|
setDepartmentDropdownOpen(false);
|
||||||
await loadCaches();
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancelCreate = () => {
|
const handleCancelCreate = () => {
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setEditingCacheId(null);
|
setEditingCacheId(null);
|
||||||
|
setDepartmentDropdownOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExpandMap = () => {
|
const handleExpandMap = () => {
|
||||||
@@ -491,6 +535,32 @@ const MapScreen = ({navigation}) => {
|
|||||||
</MapView>
|
</MapView>
|
||||||
</View>
|
</View>
|
||||||
<ScrollView style={styles.formSection}>
|
<ScrollView style={styles.formSection}>
|
||||||
|
<Text style={styles.departmentLabel}>Department</Text>
|
||||||
|
<Pressable
|
||||||
|
style={styles.departmentSelector}
|
||||||
|
onPress={() => setDepartmentDropdownOpen((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<Text style={styles.departmentSelectorText}>
|
||||||
|
{departmentNamesById[String(selectedCacheSubgroupId)] || 'Select department'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.departmentSelectorChevron}>{departmentDropdownOpen ? '▲' : '▼'}</Text>
|
||||||
|
</Pressable>
|
||||||
|
{departmentDropdownOpen && (
|
||||||
|
<View style={styles.departmentMenu}>
|
||||||
|
{departments.map((department) => (
|
||||||
|
<Pressable
|
||||||
|
key={department.SGid}
|
||||||
|
style={styles.departmentOption}
|
||||||
|
onPress={() => {
|
||||||
|
setSelectedCacheSubgroupId(department.SGid);
|
||||||
|
setDepartmentDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.departmentOptionText}>{department.SubGroupName}</Text>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.formInput}
|
style={styles.formInput}
|
||||||
placeholder="Cache Name"
|
placeholder="Cache Name"
|
||||||
@@ -598,6 +668,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
key={cache.id}
|
key={cache.id}
|
||||||
cache={cache}
|
cache={cache}
|
||||||
isAdmin={true}
|
isAdmin={true}
|
||||||
|
departmentName={departmentNamesById[String(cache.subgroupId)] || 'Unassigned'}
|
||||||
onEdit={handleEditCache}
|
onEdit={handleEditCache}
|
||||||
onDelete={handleDeleteCache}
|
onDelete={handleDeleteCache}
|
||||||
/>
|
/>
|
||||||
@@ -754,6 +825,31 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
|
departmentLabel: {fontSize: 13, fontWeight: '600', color: '#374151', marginBottom: 6},
|
||||||
|
departmentSelector: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#d1d5db',
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
departmentSelectorText: {color: '#1f2937', fontSize: 15, fontWeight: '500'},
|
||||||
|
departmentSelectorChevron: {color: '#6b7280', fontSize: 12, fontWeight: '700'},
|
||||||
|
departmentMenu: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e5e7eb',
|
||||||
|
borderRadius: 8,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
marginBottom: 10,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
departmentOption: {paddingHorizontal: 12, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f3f4f6'},
|
||||||
|
departmentOptionText: {color: '#374151', fontSize: 14},
|
||||||
coordRow: {flexDirection: 'row', gap: 10},
|
coordRow: {flexDirection: 'row', gap: 10},
|
||||||
coordField: {flex: 1},
|
coordField: {flex: 1},
|
||||||
coordLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 4},
|
coordLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 4},
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ const useGameHook = () => {
|
|||||||
|
|
||||||
// Look up a group by its Organisation Join Code
|
// Look up a group by its Organisation Join Code
|
||||||
const getGroupByOrgCode = async (code) => {
|
const getGroupByOrgCode = async (code) => {
|
||||||
const response = await API.get(`${groupsEndpoint}?OrgJoinCode=${code}`);
|
const normalizedCode = String(code || '').trim().toUpperCase();
|
||||||
|
if (!normalizedCode) return null;
|
||||||
|
const response = await API.get(`${groupsEndpoint}?OrgJoinCode=${encodeURIComponent(normalizedCode)}`);
|
||||||
if (response.isSuccess && response.result.length > 0) return response.result[0];
|
if (response.isSuccess && response.result.length > 0) return response.result[0];
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -76,7 +78,14 @@ const useGameHook = () => {
|
|||||||
|
|
||||||
// Subgroup members
|
// Subgroup members
|
||||||
const joinPrivateGame = async (payload) => {
|
const joinPrivateGame = async (payload) => {
|
||||||
const response = await API.post(subgroupMembersEndpoint, payload);
|
const data = {
|
||||||
|
JoinCode: payload.JoinCode,
|
||||||
|
Uid: payload.Uid,
|
||||||
|
};
|
||||||
|
if (payload.ExpectedGid !== undefined && payload.ExpectedGid !== null) {
|
||||||
|
data.ExpectedGid = payload.ExpectedGid;
|
||||||
|
}
|
||||||
|
const response = await API.post(subgroupMembersEndpoint, data);
|
||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user