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 {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 -------------
|
||||
|
||||
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.nameWrap}>
|
||||
<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 ? (
|
||||
<Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text>
|
||||
) : null}
|
||||
@@ -87,6 +90,7 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 8,
|
||||
},
|
||||
clue: {fontSize: 13, color: '#6b7280', marginTop: 2},
|
||||
departmentText: {fontSize: 13, color: '#2563eb', marginTop: 2, fontWeight: '600'},
|
||||
nameWrap: {flex: 1, marginRight: 8},
|
||||
name: {fontSize: 15, fontWeight: '600', color: '#1f2937'},
|
||||
claimedText: {color: '#9ca3af'},
|
||||
|
||||
@@ -69,7 +69,10 @@ const DepartmentSettingsScreen = ({route, navigation}) => {
|
||||
};
|
||||
|
||||
const handleManageCaches = () => {
|
||||
navigation.navigate('MapScreen');
|
||||
navigation.navigate('MapScreen', {
|
||||
selectedDepartmentSGid: SGid,
|
||||
selectedDepartmentName: deptName || null,
|
||||
});
|
||||
};
|
||||
|
||||
// View -----------------------
|
||||
|
||||
@@ -254,7 +254,7 @@ const GameSettingsScreen = ({navigation}) => {
|
||||
</View>
|
||||
<View style={styles.deptCardActions}>
|
||||
<Button
|
||||
label="Open Department Game Settings"
|
||||
label="Department Game"
|
||||
onClick={() => handleEnterDepartment(sg)}
|
||||
styleButton={styles.deptOpenButton}
|
||||
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 MapScreen = ({navigation}) => {
|
||||
const MapScreen = ({navigation, route}) => {
|
||||
// Initialisation ------------
|
||||
|
||||
const session = getSession();
|
||||
@@ -25,6 +25,7 @@ const MapScreen = ({navigation}) => {
|
||||
// Stable ref to prevent infinite re-render loops
|
||||
const getCachesRef = useRef(getCaches);
|
||||
getCachesRef.current = getCaches;
|
||||
const lastAppliedRouteDepartmentRef = useRef(null);
|
||||
|
||||
// State ----------------------
|
||||
|
||||
@@ -54,6 +55,8 @@ const MapScreen = ({navigation}) => {
|
||||
const [newCoord, setNewCoord] = useState(null);
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newClue, setNewClue] = useState('');
|
||||
const [selectedCacheSubgroupId, setSelectedCacheSubgroupId] = useState(null);
|
||||
const [departmentDropdownOpen, setDepartmentDropdownOpen] = useState(false);
|
||||
|
||||
const claimDistance = groupInfo?.CacheTriggerMeters || 20;
|
||||
const isPlayer = inGame && !isAdmin;
|
||||
@@ -93,7 +96,36 @@ const MapScreen = ({navigation}) => {
|
||||
}, [inGame]);
|
||||
|
||||
// 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 currentGid = getSession().currentGid;
|
||||
@@ -234,7 +266,11 @@ const MapScreen = ({navigation}) => {
|
||||
const handleJoinDepartment = async () => {
|
||||
setJoinError('');
|
||||
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) {
|
||||
setJoinError('Invalid department code. Check the code and try again.');
|
||||
return;
|
||||
@@ -273,6 +309,8 @@ const MapScreen = ({navigation}) => {
|
||||
setNewCoord(fallback);
|
||||
setNewName('');
|
||||
setNewClue('');
|
||||
if (!selectedCacheSubgroupId && defaultMemberSGid) setSelectedCacheSubgroupId(defaultMemberSGid);
|
||||
setDepartmentDropdownOpen(false);
|
||||
setIsCreating(true);
|
||||
};
|
||||
|
||||
@@ -281,6 +319,8 @@ const MapScreen = ({navigation}) => {
|
||||
setNewCoord(cache.coordinates);
|
||||
setNewName(cache.name || '');
|
||||
setNewClue(cache.clue || '');
|
||||
setSelectedCacheSubgroupId(cache.subgroupId || defaultMemberSGid);
|
||||
setDepartmentDropdownOpen(false);
|
||||
setIsCreating(true);
|
||||
};
|
||||
|
||||
@@ -292,24 +332,28 @@ const MapScreen = ({navigation}) => {
|
||||
|
||||
const handleSaveCache = async () => {
|
||||
if (!newCoord || !newClue.trim() || !session.currentGid) return;
|
||||
const subgroupId = selectedCacheSubgroupId || defaultMemberSGid;
|
||||
if (!subgroupId) return;
|
||||
const payload = {
|
||||
gid: session.currentGid,
|
||||
name: newName.trim(),
|
||||
latitude: newCoord.latitude,
|
||||
longitude: newCoord.longitude,
|
||||
clue: newClue.trim(),
|
||||
subgroupId: defaultMemberSGid,
|
||||
subgroupId,
|
||||
};
|
||||
if (editingCacheId) payload.cacheId = editingCacheId;
|
||||
await upsertCache(payload);
|
||||
setIsCreating(false);
|
||||
setEditingCacheId(null);
|
||||
setDepartmentDropdownOpen(false);
|
||||
await loadCaches();
|
||||
};
|
||||
|
||||
const handleCancelCreate = () => {
|
||||
setIsCreating(false);
|
||||
setEditingCacheId(null);
|
||||
setDepartmentDropdownOpen(false);
|
||||
};
|
||||
|
||||
const handleExpandMap = () => {
|
||||
@@ -491,6 +535,32 @@ const MapScreen = ({navigation}) => {
|
||||
</MapView>
|
||||
</View>
|
||||
<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
|
||||
style={styles.formInput}
|
||||
placeholder="Cache Name"
|
||||
@@ -598,6 +668,7 @@ const MapScreen = ({navigation}) => {
|
||||
key={cache.id}
|
||||
cache={cache}
|
||||
isAdmin={true}
|
||||
departmentName={departmentNamesById[String(cache.subgroupId)] || 'Unassigned'}
|
||||
onEdit={handleEditCache}
|
||||
onDelete={handleDeleteCache}
|
||||
/>
|
||||
@@ -754,6 +825,31 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: '#ffffff',
|
||||
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},
|
||||
coordField: {flex: 1},
|
||||
coordLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 4},
|
||||
|
||||
@@ -43,7 +43,9 @@ const useGameHook = () => {
|
||||
|
||||
// Look up a group by its Organisation Join 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];
|
||||
return null;
|
||||
};
|
||||
@@ -76,7 +78,14 @@ const useGameHook = () => {
|
||||
|
||||
// Subgroup members
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user