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,
|
getRelativeDateString,
|
||||||
isEquipmentAvailable
|
isEquipmentAvailable
|
||||||
} from "../../../utils/EquipmentBooking";
|
} from "../../../utils/EquipmentBooking";
|
||||||
|
import {StackNavigationProp} from "@react-navigation/stack";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onPress: () => void,
|
navigation: StackNavigationProp,
|
||||||
|
userDeviceRentDates: [string, string],
|
||||||
item: Device,
|
item: Device,
|
||||||
height: number,
|
height: number,
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
|
@ -20,29 +22,73 @@ type Props = {
|
||||||
|
|
||||||
class EquipmentListItem extends React.Component<Props> {
|
class EquipmentListItem extends React.Component<Props> {
|
||||||
|
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate(nextProps: Props): boolean {
|
||||||
return false;
|
return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const colors = this.props.theme.colors;
|
const colors = this.props.theme.colors;
|
||||||
const item = this.props.item;
|
const item = this.props.item;
|
||||||
|
const userDeviceRentDates = this.props.userDeviceRentDates;
|
||||||
|
const isRented = userDeviceRentDates != null;
|
||||||
const isAvailable = isEquipmentAvailable(item);
|
const isAvailable = isEquipmentAvailable(item);
|
||||||
const firstAvailability = getFirstEquipmentAvailability(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 (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={item.name}
|
title={item.name}
|
||||||
description={isAvailable
|
description={description}
|
||||||
? i18n.t('equipmentScreen.bail', {cost: item.caution})
|
onPress={onPress}
|
||||||
: i18n.t('equipmentScreen.available', {date: getRelativeDateString(firstAvailability)})}
|
|
||||||
onPress={this.props.onPress}
|
|
||||||
left={(props) => <Avatar.Icon
|
left={(props) => <Avatar.Icon
|
||||||
{...props}
|
{...props}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
icon={isAvailable ? "check-circle-outline" : "update"}
|
icon={icon}
|
||||||
color={isAvailable ? colors.success : colors.primary}
|
color={color}
|
||||||
/>}
|
/>}
|
||||||
right={(props) => <Avatar.Icon
|
right={(props) => <Avatar.Icon
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import BugReportScreen from "../screens/Other/FeedbackScreen";
|
||||||
import WebsiteScreen from "../screens/Services/WebsiteScreen";
|
import WebsiteScreen from "../screens/Services/WebsiteScreen";
|
||||||
import EquipmentScreen from "../screens/Amicale/Equipment/EquipmentListScreen";
|
import EquipmentScreen from "../screens/Amicale/Equipment/EquipmentListScreen";
|
||||||
import EquipmentLendScreen from "../screens/Amicale/Equipment/EquipmentRentScreen";
|
import EquipmentLendScreen from "../screens/Amicale/Equipment/EquipmentRentScreen";
|
||||||
|
import EquipmentConfirmScreen from "../screens/Amicale/Equipment/EquipmentConfirmScreen";
|
||||||
|
|
||||||
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
|
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("profile", MainStack, ProfileScreen, i18n.t('screens.profile'))}
|
||||||
{createScreenCollapsibleStack("club-list", MainStack, ClubListScreen, i18n.t('clubs.clubList'))}
|
{createScreenCollapsibleStack("club-list", MainStack, ClubListScreen, i18n.t('clubs.clubList'))}
|
||||||
{createScreenCollapsibleStack("equipment-list", MainStack, EquipmentScreen, i18n.t('screens.equipmentList'))}
|
{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
|
<MainStack.Screen
|
||||||
name="club-information"
|
name="club-information"
|
||||||
component={ClubDisplayScreen}
|
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}>,
|
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 ICON_AMICALE = require('../../../../assets/amicale.png');
|
||||||
const LIST_ITEM_HEIGHT = 64;
|
const LIST_ITEM_HEIGHT = 64;
|
||||||
|
|
||||||
class EquipmentListScreen extends React.Component<Props> {
|
class EquipmentListScreen extends React.Component<Props> {
|
||||||
|
|
||||||
data: Array<Device>;
|
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 }) => {
|
getRenderItem = ({item}: { item: Device }) => {
|
||||||
return (
|
return (
|
||||||
<EquipmentListItem
|
<EquipmentListItem
|
||||||
onPress={() => this.props.navigation.navigate('equipment-lend', {item: item})}
|
navigation={this.props.navigation}
|
||||||
item={item}
|
item={item}
|
||||||
|
userDeviceRentDates={this.getUserDeviceRentDates(item)}
|
||||||
height={LIST_ITEM_HEIGHT}/>
|
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
|
* Gets the list header, with explains this screen's purpose
|
||||||
*
|
*
|
||||||
|
@ -78,6 +115,11 @@ class EquipmentListScreen extends React.Component<Props> {
|
||||||
if (fetchedData != null)
|
if (fetchedData != null)
|
||||||
this.data = fetchedData["devices"];
|
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;
|
const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
|
||||||
return (
|
return (
|
||||||
<Animated.FlatList
|
<Animated.FlatList
|
||||||
|
@ -100,11 +142,17 @@ class EquipmentListScreen extends React.Component<Props> {
|
||||||
return (
|
return (
|
||||||
<AuthenticatedScreen
|
<AuthenticatedScreen
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
ref={this.authRef}
|
||||||
requests={[
|
requests={[
|
||||||
{
|
{
|
||||||
link: 'location/all',
|
link: 'location/all',
|
||||||
params: {},
|
params: {},
|
||||||
mandatory: true,
|
mandatory: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: 'location/my',
|
||||||
|
params: {},
|
||||||
|
mandatory: false,
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
renderFunction={this.getScreen}
|
renderFunction={this.getScreen}
|
||||||
|
|
|
@ -7,13 +7,11 @@ import {withCollapsible} from "../../../utils/withCollapsible";
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {StackNavigationProp} from "@react-navigation/stack";
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||||
import type {Device} from "./EquipmentListScreen";
|
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 * as Animatable from "react-native-animatable";
|
||||||
import {View} from "react-native-animatable";
|
|
||||||
import i18n from "i18n-js";
|
import i18n from "i18n-js";
|
||||||
import {CalendarList} from "react-native-calendars";
|
import {CalendarList} from "react-native-calendars";
|
||||||
import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
|
import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
|
||||||
import ConnectionManager from "../../../managers/ConnectionManager";
|
|
||||||
import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
|
import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
|
||||||
import {
|
import {
|
||||||
generateMarkedDates,
|
generateMarkedDates,
|
||||||
|
@ -23,6 +21,7 @@ import {
|
||||||
getValidRange,
|
getValidRange,
|
||||||
isEquipmentAvailable
|
isEquipmentAvailable
|
||||||
} from "../../../utils/EquipmentBooking";
|
} from "../../../utils/EquipmentBooking";
|
||||||
|
import ConnectionManager from "../../../managers/ConnectionManager";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
|
@ -228,6 +227,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
const start = this.getBookStartDate();
|
const start = this.getBookStartDate();
|
||||||
const end = this.getBookEndDate();
|
const end = this.getBookEndDate();
|
||||||
if (item != null && start != null && end != null) {
|
if (item != null && start != null && end != null) {
|
||||||
|
console.log({
|
||||||
|
"device": item.id,
|
||||||
|
"begin": getISODate(start),
|
||||||
|
"end": getISODate(end),
|
||||||
|
})
|
||||||
ConnectionManager.getInstance().authenticatedRequest(
|
ConnectionManager.getInstance().authenticatedRequest(
|
||||||
"location/booking",
|
"location/booking",
|
||||||
{
|
{
|
||||||
|
@ -236,7 +240,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
"end": getISODate(end),
|
"end": getISODate(end),
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Success, replace screen");
|
this.onDialogDismiss();
|
||||||
|
this.props.navigation.replace("equipment-confirm", {
|
||||||
|
item: this.item,
|
||||||
|
dates: [getISODate(start), getISODate(end)]
|
||||||
|
});
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((error: number) => {
|
.catch((error: number) => {
|
||||||
|
@ -244,8 +252,10 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
this.showErrorDialog(error);
|
this.showErrorDialog(error);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
} else
|
} else {
|
||||||
|
this.onDialogDismiss();
|
||||||
resolve();
|
resolve();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
"feedback": "Feedback",
|
"feedback": "Feedback",
|
||||||
"insaAccount": "INSA Account",
|
"insaAccount": "INSA Account",
|
||||||
"equipmentList": "Equipment Booking",
|
"equipmentList": "Equipment Booking",
|
||||||
"equipmentLend": "Book"
|
"equipmentLend": "Book",
|
||||||
|
"equipmentConfirm": "Confirmation"
|
||||||
},
|
},
|
||||||
"intro": {
|
"intro": {
|
||||||
"slideMain": {
|
"slideMain": {
|
||||||
|
@ -464,6 +465,7 @@
|
||||||
"bookButton": "Book selected dates",
|
"bookButton": "Book selected dates",
|
||||||
"dialogTitle": "Confirm booking?",
|
"dialogTitle": "Confirm booking?",
|
||||||
"dialogTitleLoading": "Sending your 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