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

View File

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

View File

@ -1,20 +1,29 @@
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 { useChallengeActions } from "@/hooks/useChallengeActions"
import { useChallenges } from "@/hooks/useChallenges"
import { useGame } from "@/hooks/useGame"
import { Challenge } from "@/utils/features/challenges/challengesSlice"
import { FontAwesome6 } from "@expo/vector-icons"
import { useQueryClient } from "@tanstack/react-query"
import { useRouter } from "expo-router"
import { useState } from "react"
import React, { ReactNode, useMemo, useState } from "react"
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() {
const router = useRouter()
const queryClient = useQueryClient()
const auth = useAuth()
const game = useGame()
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 [editChallengeTitle, setEditChallengeTitle] = useState("")
@ -23,6 +32,8 @@ export default function ChallengesList() {
const [editChallengeId, setEditChallengeId] = useState<number |null>(null)
const [displayedChallenge, setDisplayedChallenge] = useState<Challenge | null>(null)
const [confirmDeletedVisible, setConfirmDeleteVisible] = useState(false)
const [challengeToAttach, setChallengeToAttach] = useState<Challenge | null>(null)
const [successSnackbarVisible, setSuccessSnackbarVisible] = useState(false)
const [successMessage, setSuccessMessage] = useState("")
const [errorVisible, setErrorVisible] = useState(false)
@ -80,6 +91,22 @@ export default function ChallengesList() {
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() {
if (editChallengeId) {
@ -103,17 +130,26 @@ export default function ChallengesList() {
displayedChallenge && deleteChallengeMutation.mutate(displayedChallenge)
}
function sendAttachNewChallenge() {
if (!challengeToAttach || !currentChallengeAction) return
attachNewChallengeMutation.mutate({ challengeActionId: currentChallengeAction.id, newChallengeId: challengeToAttach.id })
}
return (
<Surface style={{ flex: 1 }}>
<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.Header>
<FlatList
data={challenges}
keyExtractor={(challenge) => `challenge-list-item-${challenge.id}`}
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
key='success-snackbar'
visible={successSnackbarVisible}
@ -206,18 +242,53 @@ export default function ChallengesList() {
<Button onPress={sendDeleteChallenge} disabled={deleteChallengeMutation.isPending}>Confirmer</Button>
</Dialog.Actions>
</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>
</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 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 (
<List.Item
title={challenge.title}
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) => {
return useMutation({
mutationFn: async (challengeActionId: number) => {

View File

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