|
|
|
@ -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} />
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|