Added booking confirmation screen

This commit is contained in:
Arnaud Vergnet 2020-07-10 17:04:29 +02:00
parent e048035722
commit 976684dfce
6 changed files with 241 additions and 18 deletions

View file

@ -10,9 +10,11 @@ import {
getRelativeDateString,
isEquipmentAvailable
} from "../../../utils/EquipmentBooking";
import {StackNavigationProp} from "@react-navigation/stack";
type Props = {
onPress: () => void,
navigation: StackNavigationProp,
userDeviceRentDates: [string, string],
item: Device,
height: number,
theme: CustomTheme,
@ -20,29 +22,73 @@ type Props = {
class EquipmentListItem extends React.Component<Props> {
shouldComponentUpdate() {
return false;
shouldComponentUpdate(nextProps: Props): boolean {
return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
}
render() {
const colors = this.props.theme.colors;
const item = this.props.item;
const userDeviceRentDates = this.props.userDeviceRentDates;
const isRented = userDeviceRentDates != null;
const isAvailable = isEquipmentAvailable(item);
const firstAvailability = getFirstEquipmentAvailability(item);
let onPress;
if (isRented)
onPress = () => this.props.navigation.navigate("equipment-confirm", {
item: item,
dates: userDeviceRentDates
});
else
onPress = () => this.props.navigation.navigate("equipment-rent", {item: item});
let description;
if (isRented) {
const start = new Date(userDeviceRentDates[0]);
const end = new Date(userDeviceRentDates[1]);
if (start.getTime() !== end.getTime())
description = i18n.t('equipmentScreen.bookingPeriod', {
begin: getRelativeDateString(start),
end: getRelativeDateString(end)
});
else
description = i18n.t('equipmentScreen.bookingDay', {
date: getRelativeDateString(start)
});
} else if (isAvailable)
description = i18n.t('equipmentScreen.bail', {cost: item.caution});
else
description = i18n.t('equipmentScreen.available', {date: getRelativeDateString(firstAvailability)});
let icon;
if (isRented)
icon = "bookmark-check";
else if (isAvailable)
icon = "check-circle-outline";
else
icon = "update";
let color;
if (isRented)
color = colors.warning;
else if (isAvailable)
color = colors.success;
else
color = colors.primary;
return (
<List.Item
title={item.name}
description={isAvailable
? i18n.t('equipmentScreen.bail', {cost: item.caution})
: i18n.t('equipmentScreen.available', {date: getRelativeDateString(firstAvailability)})}
onPress={this.props.onPress}
description={description}
onPress={onPress}
left={(props) => <Avatar.Icon
{...props}
style={{
backgroundColor: 'transparent',
}}
icon={isAvailable ? "check-circle-outline" : "update"}
color={isAvailable ? colors.success : colors.primary}
icon={icon}
color={color}
/>}
right={(props) => <Avatar.Icon
{...props}

View file

@ -25,6 +25,7 @@ import BugReportScreen from "../screens/Other/FeedbackScreen";
import WebsiteScreen from "../screens/Services/WebsiteScreen";
import EquipmentScreen from "../screens/Amicale/Equipment/EquipmentListScreen";
import EquipmentLendScreen from "../screens/Amicale/Equipment/EquipmentRentScreen";
import EquipmentConfirmScreen from "../screens/Amicale/Equipment/EquipmentConfirmScreen";
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
@ -122,7 +123,8 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
{createScreenCollapsibleStack("profile", MainStack, ProfileScreen, i18n.t('screens.profile'))}
{createScreenCollapsibleStack("club-list", MainStack, ClubListScreen, i18n.t('clubs.clubList'))}
{createScreenCollapsibleStack("equipment-list", MainStack, EquipmentScreen, i18n.t('screens.equipmentList'))}
{createScreenCollapsibleStack("equipment-lend", MainStack, EquipmentLendScreen, i18n.t('screens.equipmentLend'))}
{createScreenCollapsibleStack("equipment-rent", MainStack, EquipmentLendScreen, i18n.t('screens.equipmentLend'))}
{createScreenCollapsibleStack("equipment-confirm", MainStack, EquipmentConfirmScreen, i18n.t('screens.equipmentConfirm'))}
<MainStack.Screen
name="club-information"
component={ClubDisplayScreen}

View file

@ -0,0 +1,115 @@
// @flow
import * as React from 'react';
import {Button, Caption, Card, Headline, Paragraph, withTheme} from 'react-native-paper';
import {Collapsible} from "react-navigation-collapsible";
import {withCollapsible} from "../../../utils/withCollapsible";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {Device} from "./EquipmentListScreen";
import {Animated, View} from "react-native";
import i18n from "i18n-js";
import {getRelativeDateString} from "../../../utils/EquipmentBooking";
type Props = {
navigation: StackNavigationProp,
route: {
params?: {
item?: Device,
dates: [string, string]
},
},
theme: CustomTheme,
collapsibleStack: Collapsible,
}
class EquipmentConfirmScreen extends React.Component<Props> {
item: Device | null;
dates: [string, string] | null;
constructor(props: Props) {
super(props);
if (this.props.route.params != null) {
if (this.props.route.params.item != null)
this.item = this.props.route.params.item;
else
this.item = null;
if (this.props.route.params.dates != null)
this.dates = this.props.route.params.dates;
else
this.dates = null;
}
}
render() {
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
const item = this.item;
const dates = this.dates;
if (item != null && dates != null) {
const start = new Date(dates[0]);
const end = new Date(dates[1]);
return (
<Animated.ScrollView
// Animations
onScroll={onScroll}
contentContainerStyle={{
paddingTop: containerPaddingTop,
minHeight: '100%'
}}
scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
<Card style={{margin: 5}}>
<Card.Content>
<View style={{flex: 1}}>
<View style={{
marginLeft: "auto",
marginRight: "auto",
flexDirection: "row",
flexWrap: "wrap",
}}>
<Headline style={{textAlign: "center"}}>
{item.name}
</Headline>
<Caption style={{
textAlign: "center",
lineHeight: 35,
marginLeft: 10,
}}>
({i18n.t('equipmentScreen.bail', {cost: item.caution})})
</Caption>
</View>
</View>
<Button
icon={"check-circle-outline"}
color={this.props.theme.colors.success}
mode="text"
>
{
start == null
? i18n.t('equipmentScreen.booking')
: end != null && start.getTime() !== end.getTime()
? i18n.t('equipmentScreen.bookingPeriod', {
begin: getRelativeDateString(start),
end: getRelativeDateString(end)
})
: i18n.t('equipmentScreen.bookingDay', {
date: getRelativeDateString(start)
})
}
</Button>
<Paragraph style={{textAlign: "center"}}>
{i18n.t("equipmentScreen.bookingConfirmedMessage")}
</Paragraph>
</Card.Content>
</Card>
</Animated.ScrollView>
);
} else
return null;
}
}
export default withCollapsible(withTheme(EquipmentConfirmScreen));

View file

@ -25,22 +25,59 @@ export type Device = {
booked_at: Array<{begin: string, end: string}>,
};
export type RentedDevice = {
device_id: number,
device_name: string,
begin: string,
end: string,
}
const ICON_AMICALE = require('../../../../assets/amicale.png');
const LIST_ITEM_HEIGHT = 64;
class EquipmentListScreen extends React.Component<Props> {
data: Array<Device>;
userRents: Array<RentedDevice>;
authRef: { current: null | AuthenticatedScreen };
canRefresh: boolean;
constructor(props: Props) {
super(props);
this.canRefresh = false;
this.authRef = React.createRef();
this.props.navigation.addListener('focus', this.onScreenFocus);
}
onScreenFocus = () => {
if (this.canRefresh && this.authRef.current != null)
this.authRef.current.reload();
this.canRefresh = true;
};
getRenderItem = ({item}: { item: Device }) => {
return (
<EquipmentListItem
onPress={() => this.props.navigation.navigate('equipment-lend', {item: item})}
navigation={this.props.navigation}
item={item}
userDeviceRentDates={this.getUserDeviceRentDates(item)}
height={LIST_ITEM_HEIGHT}/>
);
};
getUserDeviceRentDates(item: Device) {
let dates = null;
for (let i = 0; i < this.userRents.length; i++) {
let device = this.userRents[i];
if (item.id === device.device_id) {
dates = [device.begin, device.end];
break;
}
}
return dates;
}
/**
* Gets the list header, with explains this screen's purpose
*
@ -78,6 +115,11 @@ class EquipmentListScreen extends React.Component<Props> {
if (fetchedData != null)
this.data = fetchedData["devices"];
}
if (data[1] != null) {
const fetchedData = data[1];
if (fetchedData != null)
this.userRents = fetchedData["locations"];
}
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
return (
<Animated.FlatList
@ -100,11 +142,17 @@ class EquipmentListScreen extends React.Component<Props> {
return (
<AuthenticatedScreen
{...this.props}
ref={this.authRef}
requests={[
{
link: 'location/all',
params: {},
mandatory: true,
},
{
link: 'location/my',
params: {},
mandatory: false,
}
]}
renderFunction={this.getScreen}

View file

@ -7,13 +7,11 @@ import {withCollapsible} from "../../../utils/withCollapsible";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {Device} from "./EquipmentListScreen";
import {Animated, BackHandler} from "react-native";
import {Animated, BackHandler, View} from "react-native";
import * as Animatable from "react-native-animatable";
import {View} from "react-native-animatable";
import i18n from "i18n-js";
import {CalendarList} from "react-native-calendars";
import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
import ConnectionManager from "../../../managers/ConnectionManager";
import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
import {
generateMarkedDates,
@ -23,6 +21,7 @@ import {
getValidRange,
isEquipmentAvailable
} from "../../../utils/EquipmentBooking";
import ConnectionManager from "../../../managers/ConnectionManager";
type Props = {
navigation: StackNavigationProp,
@ -228,6 +227,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
const start = this.getBookStartDate();
const end = this.getBookEndDate();
if (item != null && start != null && end != null) {
console.log({
"device": item.id,
"begin": getISODate(start),
"end": getISODate(end),
})
ConnectionManager.getInstance().authenticatedRequest(
"location/booking",
{
@ -236,7 +240,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
"end": getISODate(end),
})
.then(() => {
console.log("Success, replace screen");
this.onDialogDismiss();
this.props.navigation.replace("equipment-confirm", {
item: this.item,
dates: [getISODate(start), getISODate(end)]
});
resolve();
})
.catch((error: number) => {
@ -244,8 +252,10 @@ class EquipmentRentScreen extends React.Component<Props, State> {
this.showErrorDialog(error);
resolve();
});
} else
} else {
this.onDialogDismiss();
resolve();
}
});
}

View file

@ -28,7 +28,8 @@
"feedback": "Feedback",
"insaAccount": "INSA Account",
"equipmentList": "Equipment Booking",
"equipmentLend": "Book"
"equipmentLend": "Book",
"equipmentConfirm": "Confirmation"
},
"intro": {
"slideMain": {
@ -464,6 +465,7 @@
"bookButton": "Book selected dates",
"dialogTitle": "Confirm booking?",
"dialogTitleLoading": "Sending your booking...",
"dialogMessage": "Are you sure you want to confirm your booking?\n\nYou will then be able to claim the selected equipment at the Amicale for the duration of your booking in exchange of a bail."
"dialogMessage": "Are you sure you want to confirm your booking?\n\nYou will then be able to claim the selected equipment at the Amicale for the duration of your booking in exchange of a bail.",
"bookingConfirmedMessage": "Do not forget to come by the Amicale to give your bail in exchange of the equipment."
}
}