Ajout possibilité modifier son défi en cours

This commit is contained in:
Emmy D'Anello 2024-12-14 17:18:41 +01:00
parent 3348979738
commit 0f16edd8cc
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
5 changed files with 114 additions and 13 deletions

View File

@ -5,7 +5,7 @@ import { useAuth } from '@/hooks/useAuth'
import { useChallengeActions } from '@/hooks/useChallengeActions' import { useChallengeActions } from '@/hooks/useChallengeActions'
import { useChallenges } from '@/hooks/useChallenges' import { useChallenges } from '@/hooks/useChallenges'
import { useGame } from '@/hooks/useGame' import { useGame } from '@/hooks/useGame'
import { FontAwesome6 } from '@expo/vector-icons' import { FontAwesome6, MaterialCommunityIcons } from '@expo/vector-icons'
import { useQueryClient } from '@tanstack/react-query' import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'expo-router' import { useRouter } from 'expo-router'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
@ -91,10 +91,11 @@ function ChallengeScreenBody() {
style={{ flex: 1, margin: 20 }} />} style={{ flex: 1, margin: 20 }} />}
{!loading && !game.penaltyEnd && !currentChallenge && game.currentRunner && <> {!loading && !game.penaltyEnd && !currentChallenge && game.currentRunner && <>
<Banner <Banner
elevation={4}
visible={!currentChallenge && game.currentRunner && !loading} visible={!currentChallenge && game.currentRunner && !loading}
icon='cancel' icon='vanish'>
style={{ backgroundColor: MD3Colors.error40 }}> Aucun défi n'est en cours. Veuillez tirer un défi en cliquant sur le bouton central.
Aucun défi n'est en cours. Pour rappel, il faut être hors d'un train pour tirer un défi.
</Banner> </Banner>
<View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}> <View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}>
<FAB <FAB

View File

@ -68,7 +68,6 @@ export default function HistoryScreen() {
}) })
const gameRepairMutation = useGameRepairMutation({ const gameRepairMutation = useGameRepairMutation({
auth, auth,
updateGameState,
onPostSuccess: () => { onPostSuccess: () => {
setSuccessVisible(true) setSuccessVisible(true)
setSuccessMessage("Réparation du jeu effectuée avec succès") setSuccessMessage("Réparation du jeu effectuée avec succès")

View File

@ -1,20 +1,29 @@
import ChallengeCard from "@/components/ChallengeCard" import ChallengeCard from "@/components/ChallengeCard"
import { useAddChallengeMutation, useDeleteChallengeMutation, useEditChallengeMutation } from "@/hooks/mutations/useChallengeMutation" import { useAddChallengeMutation, useAttachNewChallenge, useDeleteChallengeMutation, useEditChallengeMutation } from "@/hooks/mutations/useChallengeMutation"
import { useAuth } from "@/hooks/useAuth" import { useAuth } from "@/hooks/useAuth"
import { useChallengeActions } from "@/hooks/useChallengeActions"
import { useChallenges } from "@/hooks/useChallenges" import { useChallenges } from "@/hooks/useChallenges"
import { useGame } from "@/hooks/useGame"
import { Challenge } from "@/utils/features/challenges/challengesSlice" import { Challenge } from "@/utils/features/challenges/challengesSlice"
import { FontAwesome6 } from "@expo/vector-icons" import { FontAwesome6 } from "@expo/vector-icons"
import { useQueryClient } from "@tanstack/react-query" import { useQueryClient } from "@tanstack/react-query"
import { useRouter } from "expo-router" import { useRouter } from "expo-router"
import { useState } from "react" import React, { ReactNode, useMemo, useState } from "react"
import { FlatList, StyleSheet } from "react-native" import { FlatList, StyleSheet } from "react-native"
import { Appbar, Button, Dialog, Divider, FAB, List, MD3Colors, Modal, Portal, Snackbar, Surface, Text, TextInput } from "react-native-paper" import { ActivityIndicator, Appbar, Button, Dialog, Divider, FAB, List, MD3Colors, Modal, Portal, Snackbar, Surface, Text, TextInput, Tooltip } from "react-native-paper"
export default function ChallengesList() { export default function ChallengesList() {
const router = useRouter() const router = useRouter()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const auth = useAuth() const auth = useAuth()
const game = useGame()
const challenges = useChallenges() const challenges = useChallenges()
const challengeActions = useChallengeActions()
const currentChallengeAction = useMemo(() => {
if (!game.activeChallengeId)
return null
return challengeActions.find((action) => action.id === game.activeChallengeId)
}, [game, challengeActions])
const [editChallengeVisible, setEditChallengeVisible] = useState(false) const [editChallengeVisible, setEditChallengeVisible] = useState(false)
const [editChallengeTitle, setEditChallengeTitle] = useState("") const [editChallengeTitle, setEditChallengeTitle] = useState("")
@ -23,6 +32,8 @@ export default function ChallengesList() {
const [editChallengeId, setEditChallengeId] = useState<number |null>(null) const [editChallengeId, setEditChallengeId] = useState<number |null>(null)
const [displayedChallenge, setDisplayedChallenge] = useState<Challenge | null>(null) const [displayedChallenge, setDisplayedChallenge] = useState<Challenge | null>(null)
const [confirmDeletedVisible, setConfirmDeleteVisible] = useState(false) const [confirmDeletedVisible, setConfirmDeleteVisible] = useState(false)
const [challengeToAttach, setChallengeToAttach] = useState<Challenge | null>(null)
const [successSnackbarVisible, setSuccessSnackbarVisible] = useState(false) const [successSnackbarVisible, setSuccessSnackbarVisible] = useState(false)
const [successMessage, setSuccessMessage] = useState("") const [successMessage, setSuccessMessage] = useState("")
const [errorVisible, setErrorVisible] = useState(false) const [errorVisible, setErrorVisible] = useState(false)
@ -80,6 +91,22 @@ export default function ChallengesList() {
setError([400, error.message]) setError([400, error.message])
}, },
}) })
const attachNewChallengeMutation = useAttachNewChallenge({
auth,
onPostSuccess: () => {
setChallengeToAttach(null)
setSuccessMessage("Le défi en cours a bien été modifié !")
queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === 'get-challenges' })
},
onError: ({ response, error }) => {
setChallengeToAttach(null)
setErrorVisible(true)
if (response)
setError([response.statusCode, response.message])
else if (error)
setError([400, error.message])
},
})
function sendEditChallenge() { function sendEditChallenge() {
if (editChallengeId) { if (editChallengeId) {
@ -103,17 +130,26 @@ export default function ChallengesList() {
displayedChallenge && deleteChallengeMutation.mutate(displayedChallenge) displayedChallenge && deleteChallengeMutation.mutate(displayedChallenge)
} }
function sendAttachNewChallenge() {
if (!challengeToAttach || !currentChallengeAction) return
attachNewChallengeMutation.mutate({ challengeActionId: currentChallengeAction.id, newChallengeId: challengeToAttach.id })
}
return ( return (
<Surface style={{ flex: 1 }}> <Surface style={{ flex: 1 }}>
<Appbar.Header> <Appbar.Header>
{router.canGoBack() ? <Appbar.BackAction onPress={() => router.back()} /> : undefined} <Appbar.BackAction onPress={() => router.canGoBack() ? router.back() : router.navigate('/(tabs)/challenges')} />
<Appbar.Content title={"Liste des défis"} /> <Appbar.Content title={"Liste des défis"} />
</Appbar.Header> </Appbar.Header>
<FlatList <FlatList
data={challenges} data={challenges}
keyExtractor={(challenge) => `challenge-list-item-${challenge.id}`} keyExtractor={(challenge) => `challenge-list-item-${challenge.id}`}
ItemSeparatorComponent={() => <Divider />} ItemSeparatorComponent={() => <Divider />}
renderItem={(item) => <ChallengeListItem challenge={item.item} onPress={() => setDisplayedChallenge(item.item)} />} /> renderItem={(item) =>
<ChallengeListItem challenge={item.item}
activeRunner={game.currentRunner}
onPress={() => setDisplayedChallenge(item.item)}
onLongPress={!currentChallengeAction ? undefined : () => setChallengeToAttach(item.item)} />} />
<Snackbar <Snackbar
key='success-snackbar' key='success-snackbar'
visible={successSnackbarVisible} visible={successSnackbarVisible}
@ -206,18 +242,53 @@ export default function ChallengesList() {
<Button onPress={sendDeleteChallenge} disabled={deleteChallengeMutation.isPending}>Confirmer</Button> <Button onPress={sendDeleteChallenge} disabled={deleteChallengeMutation.isPending}>Confirmer</Button>
</Dialog.Actions> </Dialog.Actions>
</Dialog> </Dialog>
<Dialog visible={challengeToAttach !== null} onDismiss={() => setChallengeToAttach(null)}>
<Dialog.Title>Traiter ce défi</Dialog.Title>
<Dialog.Content>
<Text variant='bodyMedium'>
Voulez-vous vraiment remplacer votre défi actuel par le défi « {challengeToAttach?.title} » ?
</Text>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={() => setChallengeToAttach(null)}>Annuler</Button>
<Button onPress={sendAttachNewChallenge} disabled={attachNewChallengeMutation.isPending}>Confirmer</Button>
</Dialog.Actions>
</Dialog>
</Portal> </Portal>
</Surface> </Surface>
) )
} }
function ChallengeListItem({ challenge, onPress }: { challenge: Challenge, onPress?: () => void }) { type ChallengeListItemProps = {
challenge: Challenge,
activeRunner: boolean,
onPress?: () => void,
onLongPress?: () => void,
}
function ChallengeListItem({ challenge, activeRunner, onPress, onLongPress }: ChallengeListItemProps) {
const challengeActions = useChallengeActions()
const attachedAction = challengeActions.find(challengeAction => challengeAction.challengeId === challenge.id)
const description = <Text>Récompense : {challenge.reward} <FontAwesome6 name='coins' /></Text> const description = <Text>Récompense : {challenge.reward} <FontAwesome6 name='coins' /></Text>
const icon: ReactNode = useMemo(() => {
if (!activeRunner)
return undefined
else if (!attachedAction)
return <Tooltip title="Disponible"><List.Icon icon='cards' /></Tooltip>
else if (!attachedAction.end)
return <Tooltip title="En cours d'accomplissement"><ActivityIndicator /></Tooltip>
else if (attachedAction.success)
return <Tooltip title="Précédemment réussi"><List.Icon icon='emoticon-happy' /></Tooltip>
else
return <Tooltip title="Raté"><List.Icon icon='emoticon-sad' /></Tooltip>
}, [activeRunner, attachedAction])
return ( return (
<List.Item <List.Item
title={challenge.title} title={challenge.title}
description={description} description={description}
onPress={onPress} /> right={() => icon}
onPress={onPress}
onLongPress={attachedAction ? undefined : onLongPress} />
) )
} }

View File

@ -81,6 +81,36 @@ export const useEndChallenge = ({ auth, onPostSuccess, onError }: ChallengeActio
}) })
} }
export const useAttachNewChallenge = ({ auth, onPostSuccess, onError }: ChallengeActionProps) => {
return useMutation({
mutationFn: async ({ challengeActionId, newChallengeId }: { challengeActionId: number, newChallengeId: number }) => {
return fetch(`${process.env.EXPO_PUBLIC_TRAINTRAPE_MOI_SERVER}/challenge-actions/${challengeActionId}/`, {
method: "PATCH",
headers: {
"Authorization": `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
challengeId: newChallengeId,
})
}).then(resp => resp.json())
},
onSuccess: async (data) => {
if (data.statusCode) {
if (onError)
onError({ response: data })
return
}
if (onPostSuccess)
onPostSuccess()
},
onError: async (error: Error) => {
if (onError)
onError({ error: error })
}
})
}
export const useDeleteChallengeActionMutation = ({ auth, onPostSuccess, onError }: ChallengeActionProps) => { export const useDeleteChallengeActionMutation = ({ auth, onPostSuccess, onError }: ChallengeActionProps) => {
return useMutation({ return useMutation({
mutationFn: async (challengeActionId: number) => { mutationFn: async (challengeActionId: number) => {

View File

@ -37,7 +37,7 @@ export const challengesSlice = createSlice({
reward: dlChallenge.reward, reward: dlChallenge.reward,
}) })
} }
state.challenges.sort((c1, c2) => c2.id - c1.id) state.challenges.sort((c1, c2) => c1.title.localeCompare(c2.title))
}, },
}, },
}) })