149 lines
5.8 KiB
TypeScript
149 lines
5.8 KiB
TypeScript
import PenaltyBanner from '@/components/PenalyBanner'
|
|
import { useAddTrainMutation } from '@/hooks/mutations/useTrainMutation'
|
|
import { useAuth } from '@/hooks/useAuth'
|
|
import { useTrain } from '@/hooks/useTrain'
|
|
import { TrainTrip } from '@/utils/features/train/trainSlice'
|
|
import { FontAwesome6 } from '@expo/vector-icons'
|
|
import { useQueryClient } from '@tanstack/react-query'
|
|
import { useShareIntentContext } from 'expo-share-intent'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { FlatList, StyleSheet } from 'react-native'
|
|
import { Button, Dialog, Divider, FAB, HelperText, List, MD3Colors, Portal, Snackbar, Surface, Text, TextInput } from 'react-native-paper'
|
|
|
|
export default function TrainScreen() {
|
|
const [addTrainVisible, setAddTrainVisible] = useState(false)
|
|
const [addTrainUrl, setAddTrainUrl] = useState("")
|
|
const trainId = useMemo(() => /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/.exec(addTrainUrl)?.[0], [addTrainUrl])
|
|
|
|
const [successSnackbarVisible, setSuccessSnackbarVisible] = useState(false)
|
|
const [errorVisible, setErrorVisible] = useState(false)
|
|
const [error, setError] = useState([200, ""])
|
|
|
|
const auth = useAuth()
|
|
const queryClient = useQueryClient()
|
|
const addTrainMutation = useAddTrainMutation({
|
|
auth,
|
|
onPostSuccess: () => {
|
|
setAddTrainVisible(false)
|
|
setSuccessSnackbarVisible(true)
|
|
queryClient.invalidateQueries({ predicate: (query) => query.queryKey[0] === 'get-trains' })
|
|
},
|
|
onError: ({ response, error }) => {
|
|
setErrorVisible(true)
|
|
if (response)
|
|
setError([response.statusCode, response.message])
|
|
else if (error)
|
|
setError([400, error.message])
|
|
},
|
|
})
|
|
|
|
const trains = useTrain()
|
|
|
|
|
|
const { hasShareIntent, shareIntent, resetShareIntent } = useShareIntentContext()
|
|
useEffect(() => {
|
|
if (hasShareIntent) {
|
|
resetShareIntent()
|
|
if (!shareIntent.text || !shareIntent.text.includes("eurailapp.com/share"))
|
|
return
|
|
const parsedTrainId = /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/.exec(shareIntent.text)?.[0]
|
|
if (parsedTrainId)
|
|
addTrainMutation.mutate(parsedTrainId)
|
|
else {
|
|
setErrorVisible(true)
|
|
setError([400, "Impossible de récupérer l'identifiant du train à ajouter"])
|
|
}
|
|
}
|
|
}, [hasShareIntent])
|
|
|
|
return (
|
|
<Surface style={{ flex: 1 }}>
|
|
<PenaltyBanner />
|
|
<FlatList
|
|
data={trains}
|
|
keyExtractor={(train) => train.id}
|
|
ItemSeparatorComponent={() => <Divider />}
|
|
renderItem={(item) => <TrainListItem train={item.item} />} />
|
|
<FAB
|
|
icon='plus'
|
|
style={styles.addTrainButton}
|
|
onPress={() => setAddTrainVisible(true)} />
|
|
<Snackbar
|
|
key='success-snackbar'
|
|
visible={successSnackbarVisible}
|
|
icon={'close'}
|
|
onDismiss={() => setSuccessSnackbarVisible(false)}
|
|
onIconPress={() => setSuccessSnackbarVisible(false)}>
|
|
Train ajouté avec succès
|
|
</Snackbar>
|
|
<Snackbar
|
|
key='error-snackbar'
|
|
visible={errorVisible}
|
|
icon={'close'}
|
|
onDismiss={() => setErrorVisible(false)}
|
|
onIconPress={() => setErrorVisible(false)}>
|
|
<Text variant='bodyMedium' style={{ color: MD3Colors.secondary0 }}>
|
|
Erreur {error[0]} : {error[1]}
|
|
</Text>
|
|
</Snackbar>
|
|
<Portal>
|
|
<Dialog visible={addTrainVisible} onDismiss={() => setAddTrainVisible(false)}>
|
|
<Dialog.Title>Ajout d'un train</Dialog.Title>
|
|
<Dialog.Content>
|
|
<TextInput
|
|
label="URL de partage RailPlanner"
|
|
autoComplete='url'
|
|
inputMode='url'
|
|
defaultValue={addTrainUrl}
|
|
multiline={true}
|
|
onChangeText={setAddTrainUrl}
|
|
error={!trainId}
|
|
onEndEditing={() => {
|
|
if (trainId !== undefined)
|
|
addTrainMutation.mutate(trainId)
|
|
}}
|
|
placeholder="https://eurailapp.com/share/journey?id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX&type=list&brand=interrail" />
|
|
<HelperText type='error' visible={!trainId && addTrainVisible}>
|
|
Le champ doit contenir l'identifiant d'un voyage au format UUID. {trainId}
|
|
</HelperText>
|
|
</Dialog.Content>
|
|
<Dialog.Actions>
|
|
<Button onPress={() => setAddTrainVisible(false)}>Annuler</Button>
|
|
<Button onPress={() => {
|
|
if (trainId !== undefined)
|
|
addTrainMutation.mutate(trainId)
|
|
}} disabled={trainId === undefined || addTrainMutation.isPending}>Ajouter</Button>
|
|
</Dialog.Actions>
|
|
</Dialog>
|
|
</Portal>
|
|
</Surface>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
addTrainButton: {
|
|
position: 'absolute',
|
|
right: 25,
|
|
bottom: 25,
|
|
}
|
|
})
|
|
|
|
function TrainListItem({ train }: { train: TrainTrip }) {
|
|
const depDateTime = new Date(train.departureTime)
|
|
const depDate = depDateTime.toLocaleDateString()
|
|
const depTime = depDateTime.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
|
const arrDateTime = new Date(train.arrivalTime)
|
|
const arrTime = arrDateTime.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
|
|
const durationInMinutes = (arrDateTime.getTime() - depDateTime.getTime()) / 1000 / 60
|
|
const duration = `${Math.floor(durationInMinutes / 60).toString().padStart(2, '0')}:${Math.floor(durationInMinutes % 60).toString().padStart(2, '0')}`
|
|
const title = `${train.from} ${depTime} => ${train.to} ${arrTime} (${depDate})`
|
|
const distanceKm = Math.ceil(train.distance / 1000)
|
|
const cost = 10 * distanceKm
|
|
return <>
|
|
<List.Item
|
|
title={title}
|
|
description={<><Text>Durée : {duration}, distance : {distanceKm} km, coût : {cost}</Text> <FontAwesome6 name='coins' /></>}
|
|
/>
|
|
</>
|
|
}
|