Browse Source

Added booking confirmation screen

Arnaud Vergnet 1 year ago
parent
commit
976684dfce

+ 55
- 9
src/components/Lists/Equipment/EquipmentListItem.js View File

@@ -10,9 +10,11 @@ import {
10 10
     getRelativeDateString,
11 11
     isEquipmentAvailable
12 12
 } from "../../../utils/EquipmentBooking";
13
+import {StackNavigationProp} from "@react-navigation/stack";
13 14
 
14 15
 type Props = {
15
-    onPress: () => void,
16
+    navigation: StackNavigationProp,
17
+    userDeviceRentDates: [string, string],
16 18
     item: Device,
17 19
     height: number,
18 20
     theme: CustomTheme,
@@ -20,29 +22,73 @@ type Props = {
20 22
 
21 23
 class EquipmentListItem extends React.Component<Props> {
22 24
 
23
-    shouldComponentUpdate() {
24
-        return false;
25
+    shouldComponentUpdate(nextProps: Props): boolean {
26
+        return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
25 27
     }
26 28
 
27 29
     render() {
28 30
         const colors = this.props.theme.colors;
29 31
         const item = this.props.item;
32
+        const userDeviceRentDates = this.props.userDeviceRentDates;
33
+        const isRented = userDeviceRentDates != null;
30 34
         const isAvailable = isEquipmentAvailable(item);
31 35
         const firstAvailability = getFirstEquipmentAvailability(item);
36
+
37
+        let onPress;
38
+        if (isRented)
39
+            onPress = () => this.props.navigation.navigate("equipment-confirm", {
40
+                item: item,
41
+                dates: userDeviceRentDates
42
+            });
43
+        else
44
+            onPress = () => this.props.navigation.navigate("equipment-rent", {item: item});
45
+
46
+        let description;
47
+        if (isRented) {
48
+            const start = new Date(userDeviceRentDates[0]);
49
+            const end = new Date(userDeviceRentDates[1]);
50
+            if (start.getTime() !== end.getTime())
51
+                description = i18n.t('equipmentScreen.bookingPeriod', {
52
+                    begin: getRelativeDateString(start),
53
+                    end: getRelativeDateString(end)
54
+                });
55
+            else
56
+                description = i18n.t('equipmentScreen.bookingDay', {
57
+                    date: getRelativeDateString(start)
58
+                });
59
+        } else if (isAvailable)
60
+            description = i18n.t('equipmentScreen.bail', {cost: item.caution});
61
+        else
62
+            description = i18n.t('equipmentScreen.available', {date: getRelativeDateString(firstAvailability)});
63
+
64
+        let icon;
65
+        if (isRented)
66
+            icon = "bookmark-check";
67
+        else if (isAvailable)
68
+            icon = "check-circle-outline";
69
+        else
70
+            icon = "update";
71
+
72
+        let color;
73
+        if (isRented)
74
+            color = colors.warning;
75
+        else if (isAvailable)
76
+            color = colors.success;
77
+        else
78
+            color = colors.primary;
79
+
32 80
         return (
33 81
             <List.Item
34 82
                 title={item.name}
35
-                description={isAvailable
36
-                    ? i18n.t('equipmentScreen.bail', {cost: item.caution})
37
-                    : i18n.t('equipmentScreen.available', {date: getRelativeDateString(firstAvailability)})}
38
-                onPress={this.props.onPress}
83
+                description={description}
84
+                onPress={onPress}
39 85
                 left={(props) => <Avatar.Icon
40 86
                     {...props}
41 87
                     style={{
42 88
                         backgroundColor: 'transparent',
43 89
                     }}
44
-                    icon={isAvailable ? "check-circle-outline" : "update"}
45
-                    color={isAvailable ? colors.success : colors.primary}
90
+                    icon={icon}
91
+                    color={color}
46 92
                 />}
47 93
                 right={(props) => <Avatar.Icon
48 94
                     {...props}

+ 3
- 1
src/navigation/MainNavigator.js View File

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

+ 115
- 0
src/screens/Amicale/Equipment/EquipmentConfirmScreen.js View File

@@ -0,0 +1,115 @@
1
+// @flow
2
+
3
+import * as React from 'react';
4
+import {Button, Caption, Card, Headline, Paragraph, withTheme} from 'react-native-paper';
5
+import {Collapsible} from "react-navigation-collapsible";
6
+import {withCollapsible} from "../../../utils/withCollapsible";
7
+import {StackNavigationProp} from "@react-navigation/stack";
8
+import type {CustomTheme} from "../../../managers/ThemeManager";
9
+import type {Device} from "./EquipmentListScreen";
10
+import {Animated, View} from "react-native";
11
+import i18n from "i18n-js";
12
+import {getRelativeDateString} from "../../../utils/EquipmentBooking";
13
+
14
+type Props = {
15
+    navigation: StackNavigationProp,
16
+    route: {
17
+        params?: {
18
+            item?: Device,
19
+            dates: [string, string]
20
+        },
21
+    },
22
+    theme: CustomTheme,
23
+    collapsibleStack: Collapsible,
24
+}
25
+
26
+
27
+class EquipmentConfirmScreen extends React.Component<Props> {
28
+
29
+    item: Device | null;
30
+    dates: [string, string] | null;
31
+
32
+    constructor(props: Props) {
33
+        super(props);
34
+        if (this.props.route.params != null) {
35
+            if (this.props.route.params.item != null)
36
+                this.item = this.props.route.params.item;
37
+            else
38
+                this.item = null;
39
+            if (this.props.route.params.dates != null)
40
+                this.dates = this.props.route.params.dates;
41
+            else
42
+                this.dates = null;
43
+        }
44
+    }
45
+
46
+    render() {
47
+        const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
48
+        const item = this.item;
49
+        const dates = this.dates;
50
+        if (item != null && dates != null) {
51
+            const start = new Date(dates[0]);
52
+            const end = new Date(dates[1]);
53
+            return (
54
+                <Animated.ScrollView
55
+                    // Animations
56
+                    onScroll={onScroll}
57
+                    contentContainerStyle={{
58
+                        paddingTop: containerPaddingTop,
59
+                        minHeight: '100%'
60
+                    }}
61
+                    scrollIndicatorInsets={{top: scrollIndicatorInsetTop}}>
62
+                    <Card style={{margin: 5}}>
63
+                        <Card.Content>
64
+                            <View style={{flex: 1}}>
65
+                                <View style={{
66
+                                    marginLeft: "auto",
67
+                                    marginRight: "auto",
68
+                                    flexDirection: "row",
69
+                                    flexWrap: "wrap",
70
+                                }}>
71
+                                    <Headline style={{textAlign: "center"}}>
72
+                                        {item.name}
73
+                                    </Headline>
74
+                                    <Caption style={{
75
+                                        textAlign: "center",
76
+                                        lineHeight: 35,
77
+                                        marginLeft: 10,
78
+                                    }}>
79
+                                        ({i18n.t('equipmentScreen.bail', {cost: item.caution})})
80
+                                    </Caption>
81
+                                </View>
82
+                            </View>
83
+                            <Button
84
+                                icon={"check-circle-outline"}
85
+                                color={this.props.theme.colors.success}
86
+                                mode="text"
87
+                            >
88
+                                {
89
+                                    start == null
90
+                                        ? i18n.t('equipmentScreen.booking')
91
+                                        : end != null && start.getTime() !== end.getTime()
92
+                                        ? i18n.t('equipmentScreen.bookingPeriod', {
93
+                                            begin: getRelativeDateString(start),
94
+                                            end: getRelativeDateString(end)
95
+                                        })
96
+                                        : i18n.t('equipmentScreen.bookingDay', {
97
+                                            date: getRelativeDateString(start)
98
+                                        })
99
+                                }
100
+                            </Button>
101
+                            <Paragraph style={{textAlign: "center"}}>
102
+                                {i18n.t("equipmentScreen.bookingConfirmedMessage")}
103
+                            </Paragraph>
104
+                        </Card.Content>
105
+                    </Card>
106
+                </Animated.ScrollView>
107
+            );
108
+        } else
109
+            return null;
110
+
111
+    }
112
+
113
+}
114
+
115
+export default withCollapsible(withTheme(EquipmentConfirmScreen));

+ 49
- 1
src/screens/Amicale/Equipment/EquipmentListScreen.js View File

@@ -25,22 +25,59 @@ export type Device = {
25 25
     booked_at: Array<{begin: string, end: string}>,
26 26
 };
27 27
 
28
+export type RentedDevice = {
29
+    device_id: number,
30
+    device_name: string,
31
+    begin: string,
32
+    end: string,
33
+}
34
+
28 35
 const ICON_AMICALE = require('../../../../assets/amicale.png');
29 36
 const LIST_ITEM_HEIGHT = 64;
30 37
 
31 38
 class EquipmentListScreen extends React.Component<Props> {
32 39
 
33 40
     data: Array<Device>;
41
+    userRents: Array<RentedDevice>;
42
+
43
+    authRef: { current: null | AuthenticatedScreen };
44
+    canRefresh: boolean;
45
+
46
+    constructor(props: Props) {
47
+        super(props);
48
+        this.canRefresh = false;
49
+        this.authRef = React.createRef();
50
+        this.props.navigation.addListener('focus', this.onScreenFocus);
51
+    }
52
+
53
+    onScreenFocus = () => {
54
+        if (this.canRefresh && this.authRef.current != null)
55
+            this.authRef.current.reload();
56
+        this.canRefresh = true;
57
+    };
34 58
 
35 59
     getRenderItem = ({item}: { item: Device }) => {
36 60
         return (
37 61
             <EquipmentListItem
38
-                onPress={() => this.props.navigation.navigate('equipment-lend', {item: item})}
62
+                navigation={this.props.navigation}
39 63
                 item={item}
64
+                userDeviceRentDates={this.getUserDeviceRentDates(item)}
40 65
                 height={LIST_ITEM_HEIGHT}/>
41 66
         );
42 67
     };
43 68
 
69
+    getUserDeviceRentDates(item: Device) {
70
+        let dates = null;
71
+        for (let i = 0; i < this.userRents.length; i++) {
72
+            let device = this.userRents[i];
73
+            if (item.id === device.device_id) {
74
+                dates = [device.begin, device.end];
75
+                break;
76
+            }
77
+        }
78
+        return dates;
79
+    }
80
+
44 81
     /**
45 82
      * Gets the list header, with explains this screen's purpose
46 83
      *
@@ -78,6 +115,11 @@ class EquipmentListScreen extends React.Component<Props> {
78 115
             if (fetchedData != null)
79 116
                 this.data = fetchedData["devices"];
80 117
         }
118
+        if (data[1] != null) {
119
+            const fetchedData = data[1];
120
+            if (fetchedData != null)
121
+                this.userRents = fetchedData["locations"];
122
+        }
81 123
         const {containerPaddingTop, scrollIndicatorInsetTop, onScroll} = this.props.collapsibleStack;
82 124
         return (
83 125
             <Animated.FlatList
@@ -100,11 +142,17 @@ class EquipmentListScreen extends React.Component<Props> {
100 142
         return (
101 143
             <AuthenticatedScreen
102 144
                 {...this.props}
145
+                ref={this.authRef}
103 146
                 requests={[
104 147
                     {
105 148
                         link: 'location/all',
106 149
                         params: {},
107 150
                         mandatory: true,
151
+                    },
152
+                    {
153
+                        link: 'location/my',
154
+                        params: {},
155
+                        mandatory: false,
108 156
                     }
109 157
                 ]}
110 158
                 renderFunction={this.getScreen}

+ 15
- 5
src/screens/Amicale/Equipment/EquipmentRentScreen.js View File

@@ -7,13 +7,11 @@ import {withCollapsible} from "../../../utils/withCollapsible";
7 7
 import {StackNavigationProp} from "@react-navigation/stack";
8 8
 import type {CustomTheme} from "../../../managers/ThemeManager";
9 9
 import type {Device} from "./EquipmentListScreen";
10
-import {Animated, BackHandler} from "react-native";
10
+import {Animated, BackHandler, View} from "react-native";
11 11
 import * as Animatable from "react-native-animatable";
12
-import {View} from "react-native-animatable";
13 12
 import i18n from "i18n-js";
14 13
 import {CalendarList} from "react-native-calendars";
15 14
 import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
16
-import ConnectionManager from "../../../managers/ConnectionManager";
17 15
 import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
18 16
 import {
19 17
     generateMarkedDates,
@@ -23,6 +21,7 @@ import {
23 21
     getValidRange,
24 22
     isEquipmentAvailable
25 23
 } from "../../../utils/EquipmentBooking";
24
+import ConnectionManager from "../../../managers/ConnectionManager";
26 25
 
27 26
 type Props = {
28 27
     navigation: StackNavigationProp,
@@ -228,6 +227,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
228 227
             const start = this.getBookStartDate();
229 228
             const end = this.getBookEndDate();
230 229
             if (item != null && start != null && end != null) {
230
+                console.log({
231
+                    "device": item.id,
232
+                    "begin": getISODate(start),
233
+                    "end": getISODate(end),
234
+                })
231 235
                 ConnectionManager.getInstance().authenticatedRequest(
232 236
                     "location/booking",
233 237
                     {
@@ -236,7 +240,11 @@ class EquipmentRentScreen extends React.Component<Props, State> {
236 240
                         "end": getISODate(end),
237 241
                     })
238 242
                     .then(() => {
239
-                        console.log("Success, replace screen");
243
+                        this.onDialogDismiss();
244
+                        this.props.navigation.replace("equipment-confirm", {
245
+                            item: this.item,
246
+                            dates: [getISODate(start), getISODate(end)]
247
+                        });
240 248
                         resolve();
241 249
                     })
242 250
                     .catch((error: number) => {
@@ -244,8 +252,10 @@ class EquipmentRentScreen extends React.Component<Props, State> {
244 252
                         this.showErrorDialog(error);
245 253
                         resolve();
246 254
                     });
247
-            } else
255
+            } else {
256
+                this.onDialogDismiss();
248 257
                 resolve();
258
+            }
249 259
         });
250 260
     }
251 261
 

+ 4
- 2
translations/en.json View File

@@ -28,7 +28,8 @@
28 28
     "feedback": "Feedback",
29 29
     "insaAccount": "INSA Account",
30 30
     "equipmentList": "Equipment Booking",
31
-    "equipmentLend": "Book"
31
+    "equipmentLend": "Book",
32
+    "equipmentConfirm": "Confirmation"
32 33
   },
33 34
   "intro": {
34 35
     "slideMain": {
@@ -464,6 +465,7 @@
464 465
     "bookButton": "Book selected dates",
465 466
     "dialogTitle": "Confirm booking?",
466 467
     "dialogTitleLoading": "Sending your booking...",
467
-    "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."
468
+    "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.",
469
+    "bookingConfirmedMessage": "Do not forget to come by the Amicale to give your bail in exchange of the equipment."
468 470
   }
469 471
 }

Loading…
Cancel
Save