forked from vergnet/application-amicale
Added booking confirmation screen
This commit is contained in:
parent
e048035722
commit
976684dfce
6 changed files with 241 additions and 18 deletions
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
115
src/screens/Amicale/Equipment/EquipmentConfirmScreen.js
Normal file
115
src/screens/Amicale/Equipment/EquipmentConfirmScreen.js
Normal 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));
|
|
@ -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}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue