Browse Source

Improve equipment booking components to match linter

Arnaud Vergnet 3 years ago
parent
commit
11b5f2ac71

+ 97
- 96
src/components/Lists/Equipment/EquipmentListItem.js View File

@@ -2,111 +2,112 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {Avatar, List, withTheme} from 'react-native-paper';
5
-import type {CustomTheme} from "../../../managers/ThemeManager";
6
-import type {Device} from "../../../screens/Amicale/Equipment/EquipmentListScreen";
7
-import i18n from "i18n-js";
5
+import i18n from 'i18n-js';
6
+import {StackNavigationProp} from '@react-navigation/stack';
7
+import type {CustomTheme} from '../../../managers/ThemeManager';
8
+import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen';
8 9
 import {
9
-    getFirstEquipmentAvailability,
10
-    getRelativeDateString,
11
-    isEquipmentAvailable
12
-} from "../../../utils/EquipmentBooking";
13
-import {StackNavigationProp} from "@react-navigation/stack";
10
+  getFirstEquipmentAvailability,
11
+  getRelativeDateString,
12
+  isEquipmentAvailable,
13
+} from '../../../utils/EquipmentBooking';
14 14
 
15
-type Props = {
16
-    navigation: StackNavigationProp,
17
-    userDeviceRentDates: [string, string],
18
-    item: Device,
19
-    height: number,
20
-    theme: CustomTheme,
21
-}
22
-
23
-class EquipmentListItem extends React.Component<Props> {
15
+type PropsType = {
16
+  navigation: StackNavigationProp,
17
+  userDeviceRentDates: [string, string],
18
+  item: DeviceType,
19
+  height: number,
20
+  theme: CustomTheme,
21
+};
24 22
 
25
-    shouldComponentUpdate(nextProps: Props): boolean {
26
-        return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
27
-    }
23
+class EquipmentListItem extends React.Component<PropsType> {
24
+  shouldComponentUpdate(nextProps: PropsType): boolean {
25
+    const {userDeviceRentDates} = this.props;
26
+    return nextProps.userDeviceRentDates !== userDeviceRentDates;
27
+  }
28 28
 
29
-    render() {
30
-        const colors = this.props.theme.colors;
31
-        const item = this.props.item;
32
-        const userDeviceRentDates = this.props.userDeviceRentDates;
33
-        const isRented = userDeviceRentDates != null;
34
-        const isAvailable = isEquipmentAvailable(item);
35
-        const firstAvailability = getFirstEquipmentAvailability(item);
29
+  render(): React.Node {
30
+    const {item, userDeviceRentDates, navigation, height, theme} = this.props;
31
+    const isRented = userDeviceRentDates != null;
32
+    const isAvailable = isEquipmentAvailable(item);
33
+    const firstAvailability = getFirstEquipmentAvailability(item);
36 34
 
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});
35
+    let onPress;
36
+    if (isRented)
37
+      onPress = () => {
38
+        navigation.navigate('equipment-confirm', {
39
+          item,
40
+          dates: userDeviceRentDates,
41
+        });
42
+      };
43
+    else
44
+      onPress = () => {
45
+        navigation.navigate('equipment-rent', {item});
46
+      };
45 47
 
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('screens.equipment.bookingPeriod', {
52
-                    begin: getRelativeDateString(start),
53
-                    end: getRelativeDateString(end)
54
-                });
55
-            else
56
-                description = i18n.t('screens.equipment.bookingDay', {
57
-                    date: getRelativeDateString(start)
58
-                });
59
-        } else if (isAvailable)
60
-            description = i18n.t('screens.equipment.bail', {cost: item.caution});
61
-        else
62
-            description = i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)});
48
+    let description;
49
+    if (isRented) {
50
+      const start = new Date(userDeviceRentDates[0]);
51
+      const end = new Date(userDeviceRentDates[1]);
52
+      if (start.getTime() !== end.getTime())
53
+        description = i18n.t('screens.equipment.bookingPeriod', {
54
+          begin: getRelativeDateString(start),
55
+          end: getRelativeDateString(end),
56
+        });
57
+      else
58
+        description = i18n.t('screens.equipment.bookingDay', {
59
+          date: getRelativeDateString(start),
60
+        });
61
+    } else if (isAvailable)
62
+      description = i18n.t('screens.equipment.bail', {cost: item.caution});
63
+    else
64
+      description = i18n.t('screens.equipment.available', {
65
+        date: getRelativeDateString(firstAvailability),
66
+      });
63 67
 
64
-        let icon;
65
-        if (isRented)
66
-            icon = "bookmark-check";
67
-        else if (isAvailable)
68
-            icon = "check-circle-outline";
69
-        else
70
-            icon = "update";
68
+    let icon;
69
+    if (isRented) icon = 'bookmark-check';
70
+    else if (isAvailable) icon = 'check-circle-outline';
71
+    else icon = 'update';
71 72
 
72
-        let color;
73
-        if (isRented)
74
-            color = colors.warning;
75
-        else if (isAvailable)
76
-            color = colors.success;
77
-        else
78
-            color = colors.primary;
73
+    let color;
74
+    if (isRented) color = theme.colors.warning;
75
+    else if (isAvailable) color = theme.colors.success;
76
+    else color = theme.colors.primary;
79 77
 
80
-        return (
81
-            <List.Item
82
-                title={item.name}
83
-                description={description}
84
-                onPress={onPress}
85
-                left={(props) => <Avatar.Icon
86
-                    {...props}
87
-                    style={{
88
-                        backgroundColor: 'transparent',
89
-                    }}
90
-                    icon={icon}
91
-                    color={color}
92
-                />}
93
-                right={(props) => <Avatar.Icon
94
-                    {...props}
95
-                    style={{
96
-                        marginTop: 'auto',
97
-                        marginBottom: 'auto',
98
-                        backgroundColor: 'transparent',
99
-                    }}
100
-                    size={48}
101
-                    icon={"chevron-right"}
102
-                />}
103
-                style={{
104
-                    height: this.props.height,
105
-                    justifyContent: 'center',
106
-                }}
107
-            />
108
-        );
109
-    }
78
+    return (
79
+      <List.Item
80
+        title={item.name}
81
+        description={description}
82
+        onPress={onPress}
83
+        left={({size}: {size: number}): React.Node => (
84
+          <Avatar.Icon
85
+            size={size}
86
+            style={{
87
+              backgroundColor: 'transparent',
88
+            }}
89
+            icon={icon}
90
+            color={color}
91
+          />
92
+        )}
93
+        right={(): React.Node => (
94
+          <Avatar.Icon
95
+            style={{
96
+              marginTop: 'auto',
97
+              marginBottom: 'auto',
98
+              backgroundColor: 'transparent',
99
+            }}
100
+            size={48}
101
+            icon="chevron-right"
102
+          />
103
+        )}
104
+        style={{
105
+          height,
106
+          justifyContent: 'center',
107
+        }}
108
+      />
109
+    );
110
+  }
110 111
 }
111 112
 
112 113
 export default withTheme(EquipmentListItem);

+ 88
- 91
src/screens/Amicale/Equipment/EquipmentConfirmScreen.js View File

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

+ 176
- 172
src/screens/Amicale/Equipment/EquipmentListScreen.js View File

@@ -1,193 +1,197 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {View} from "react-native";
4
+import {View} from 'react-native';
5 5
 import {Button, withTheme} from 'react-native-paper';
6
-import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
7
-import {StackNavigationProp} from "@react-navigation/stack";
8
-import type {CustomTheme} from "../../../managers/ThemeManager";
9
-import i18n from "i18n-js";
10
-import type {club} from "../Clubs/ClubListScreen";
11
-import EquipmentListItem from "../../../components/Lists/Equipment/EquipmentListItem";
12
-import MascotPopup from "../../../components/Mascot/MascotPopup";
13
-import {MASCOT_STYLE} from "../../../components/Mascot/Mascot";
14
-import AsyncStorageManager from "../../../managers/AsyncStorageManager";
15
-import CollapsibleFlatList from "../../../components/Collapsible/CollapsibleFlatList";
16
-
17
-type Props = {
18
-    navigation: StackNavigationProp,
19
-    theme: CustomTheme,
20
-}
6
+import {StackNavigationProp} from '@react-navigation/stack';
7
+import i18n from 'i18n-js';
8
+import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
9
+import type {ClubType} from '../Clubs/ClubListScreen';
10
+import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
11
+import MascotPopup from '../../../components/Mascot/MascotPopup';
12
+import {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
13
+import AsyncStorageManager from '../../../managers/AsyncStorageManager';
14
+import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
15
+import type {ApiGenericDataType} from '../../../utils/WebData';
16
+
17
+type PropsType = {
18
+  navigation: StackNavigationProp,
19
+};
21 20
 
22
-type State = {
23
-    mascotDialogVisible: boolean,
24
-}
21
+type StateType = {
22
+  mascotDialogVisible: boolean,
23
+};
25 24
 
26
-export type Device = {
27
-    id: number,
28
-    name: string,
29
-    caution: number,
30
-    booked_at: Array<{ begin: string, end: string }>,
25
+export type DeviceType = {
26
+  id: number,
27
+  name: string,
28
+  caution: number,
29
+  booked_at: Array<{begin: string, end: string}>,
31 30
 };
32 31
 
33
-export type RentedDevice = {
34
-    device_id: number,
35
-    device_name: string,
36
-    begin: string,
37
-    end: string,
38
-}
32
+export type RentedDeviceType = {
33
+  device_id: number,
34
+  device_name: string,
35
+  begin: string,
36
+  end: string,
37
+};
39 38
 
40 39
 const LIST_ITEM_HEIGHT = 64;
41 40
 
42
-class EquipmentListScreen extends React.Component<Props, State> {
41
+class EquipmentListScreen extends React.Component<PropsType, StateType> {
42
+  data: Array<DeviceType>;
43 43
 
44
-    state = {
45
-        mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key),
46
-    }
47
-
48
-    data: Array<Device>;
49
-    userRents: Array<RentedDevice>;
44
+  userRents: Array<RentedDeviceType>;
50 45
 
51
-    authRef: { current: null | AuthenticatedScreen };
52
-    canRefresh: boolean;
46
+  authRef: {current: null | AuthenticatedScreen};
53 47
 
54
-    constructor(props: Props) {
55
-        super(props);
56
-        this.canRefresh = false;
57
-        this.authRef = React.createRef();
58
-        this.props.navigation.addListener('focus', this.onScreenFocus);
59
-    }
60
-
61
-    onScreenFocus = () => {
62
-        if (this.canRefresh && this.authRef.current != null)
63
-            this.authRef.current.reload();
64
-        this.canRefresh = true;
65
-    };
48
+  canRefresh: boolean;
66 49
 
67
-    getRenderItem = ({item}: { item: Device }) => {
68
-        return (
69
-            <EquipmentListItem
70
-                navigation={this.props.navigation}
71
-                item={item}
72
-                userDeviceRentDates={this.getUserDeviceRentDates(item)}
73
-                height={LIST_ITEM_HEIGHT}/>
74
-        );
50
+  constructor(props: PropsType) {
51
+    super(props);
52
+    this.state = {
53
+      mascotDialogVisible: AsyncStorageManager.getBool(
54
+        AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
55
+      ),
75 56
     };
76
-
77
-    getUserDeviceRentDates(item: Device) {
78
-        let dates = null;
79
-        for (let i = 0; i < this.userRents.length; i++) {
80
-            let device = this.userRents[i];
81
-            if (item.id === device.device_id) {
82
-                dates = [device.begin, device.end];
83
-                break;
84
-            }
85
-        }
86
-        return dates;
87
-    }
88
-
89
-    /**
90
-     * Gets the list header, with explains this screen's purpose
91
-     *
92
-     * @returns {*}
93
-     */
94
-    getListHeader() {
95
-        return (
96
-            <View style={{
97
-                width: "100%",
98
-                marginTop: 10,
99
-                marginBottom: 10,
100
-            }}>
101
-                <Button
102
-                    mode={"contained"}
103
-                    icon={"help-circle"}
104
-                    onPress={this.showMascotDialog}
105
-                    style={{
106
-                        marginRight: "auto",
107
-                        marginLeft: "auto",
108
-                    }}>
109
-                    {i18n.t("screens.equipment.mascotDialog.title")}
110
-                </Button>
111
-            </View>
112
-        );
57
+    this.canRefresh = false;
58
+    this.authRef = React.createRef();
59
+    props.navigation.addListener('focus', this.onScreenFocus);
60
+  }
61
+
62
+  onScreenFocus = () => {
63
+    if (this.canRefresh && this.authRef.current != null)
64
+      this.authRef.current.reload();
65
+    this.canRefresh = true;
66
+  };
67
+
68
+  getRenderItem = ({item}: {item: DeviceType}): React.Node => {
69
+    const {navigation} = this.props;
70
+    return (
71
+      <EquipmentListItem
72
+        navigation={navigation}
73
+        item={item}
74
+        userDeviceRentDates={this.getUserDeviceRentDates(item)}
75
+        height={LIST_ITEM_HEIGHT}
76
+      />
77
+    );
78
+  };
79
+
80
+  getUserDeviceRentDates(item: DeviceType): [number, number] | null {
81
+    let dates = null;
82
+    this.userRents.forEach((device: RentedDeviceType) => {
83
+      if (item.id === device.device_id) {
84
+        dates = [device.begin, device.end];
85
+      }
86
+    });
87
+    return dates;
88
+  }
89
+
90
+  /**
91
+   * Gets the list header, with explains this screen's purpose
92
+   *
93
+   * @returns {*}
94
+   */
95
+  getListHeader(): React.Node {
96
+    return (
97
+      <View
98
+        style={{
99
+          width: '100%',
100
+          marginTop: 10,
101
+          marginBottom: 10,
102
+        }}>
103
+        <Button
104
+          mode="contained"
105
+          icon="help-circle"
106
+          onPress={this.showMascotDialog}
107
+          style={{
108
+            marginRight: 'auto',
109
+            marginLeft: 'auto',
110
+          }}>
111
+          {i18n.t('screens.equipment.mascotDialog.title')}
112
+        </Button>
113
+      </View>
114
+    );
115
+  }
116
+
117
+  keyExtractor = (item: ClubType): string => item.id.toString();
118
+
119
+  /**
120
+   * Gets the main screen component with the fetched data
121
+   *
122
+   * @param data The data fetched from the server
123
+   * @returns {*}
124
+   */
125
+  getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
126
+    if (data[0] != null) {
127
+      const fetchedData = data[0];
128
+      if (fetchedData != null) this.data = fetchedData.devices;
113 129
     }
114
-
115
-    keyExtractor = (item: club) => item.id.toString();
116
-
117
-    /**
118
-     * Gets the main screen component with the fetched data
119
-     *
120
-     * @param data The data fetched from the server
121
-     * @returns {*}
122
-     */
123
-    getScreen = (data: Array<{ [key: string]: any } | null>) => {
124
-        if (data[0] != null) {
125
-            const fetchedData = data[0];
126
-            if (fetchedData != null)
127
-                this.data = fetchedData["devices"];
128
-        }
129
-        if (data[1] != null) {
130
-            const fetchedData = data[1];
131
-            if (fetchedData != null)
132
-                this.userRents = fetchedData["locations"];
133
-        }
134
-        return (
135
-            <CollapsibleFlatList
136
-                keyExtractor={this.keyExtractor}
137
-                renderItem={this.getRenderItem}
138
-                ListHeaderComponent={this.getListHeader()}
139
-                data={this.data}
140
-            />
141
-        )
142
-    };
143
-
144
-    showMascotDialog = () => {
145
-        this.setState({mascotDialogVisible: true})
146
-    };
147
-
148
-    hideMascotDialog = () => {
149
-        AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key, false);
150
-        this.setState({mascotDialogVisible: false})
151
-    };
152
-
153
-    render() {
154
-        return (
155
-            <View style={{flex: 1}}>
156
-                <AuthenticatedScreen
157
-                    {...this.props}
158
-                    ref={this.authRef}
159
-                    requests={[
160
-                        {
161
-                            link: 'location/all',
162
-                            params: {},
163
-                            mandatory: true,
164
-                        },
165
-                        {
166
-                            link: 'location/my',
167
-                            params: {},
168
-                            mandatory: false,
169
-                        }
170
-                    ]}
171
-                    renderFunction={this.getScreen}
172
-                />
173
-                <MascotPopup
174
-                    visible={this.state.mascotDialogVisible}
175
-                    title={i18n.t("screens.equipment.mascotDialog.title")}
176
-                    message={i18n.t("screens.equipment.mascotDialog.message")}
177
-                    icon={"vote"}
178
-                    buttons={{
179
-                        action: null,
180
-                        cancel: {
181
-                            message: i18n.t("screens.equipment.mascotDialog.button"),
182
-                            icon: "check",
183
-                            onPress: this.hideMascotDialog,
184
-                        }
185
-                    }}
186
-                    emotion={MASCOT_STYLE.WINK}
187
-                />
188
-            </View>
189
-        );
130
+    if (data[1] != null) {
131
+      const fetchedData = data[1];
132
+      if (fetchedData != null) this.userRents = fetchedData.locations;
190 133
     }
134
+    return (
135
+      <CollapsibleFlatList
136
+        keyExtractor={this.keyExtractor}
137
+        renderItem={this.getRenderItem}
138
+        ListHeaderComponent={this.getListHeader()}
139
+        data={this.data}
140
+      />
141
+    );
142
+  };
143
+
144
+  showMascotDialog = () => {
145
+    this.setState({mascotDialogVisible: true});
146
+  };
147
+
148
+  hideMascotDialog = () => {
149
+    AsyncStorageManager.set(
150
+      AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
151
+      false,
152
+    );
153
+    this.setState({mascotDialogVisible: false});
154
+  };
155
+
156
+  render(): React.Node {
157
+    const {props, state} = this;
158
+    return (
159
+      <View style={{flex: 1}}>
160
+        <AuthenticatedScreen
161
+          navigation={props.navigation}
162
+          ref={this.authRef}
163
+          requests={[
164
+            {
165
+              link: 'location/all',
166
+              params: {},
167
+              mandatory: true,
168
+            },
169
+            {
170
+              link: 'location/my',
171
+              params: {},
172
+              mandatory: false,
173
+            },
174
+          ]}
175
+          renderFunction={this.getScreen}
176
+        />
177
+        <MascotPopup
178
+          visible={state.mascotDialogVisible}
179
+          title={i18n.t('screens.equipment.mascotDialog.title')}
180
+          message={i18n.t('screens.equipment.mascotDialog.message')}
181
+          icon="vote"
182
+          buttons={{
183
+            action: null,
184
+            cancel: {
185
+              message: i18n.t('screens.equipment.mascotDialog.button'),
186
+              icon: 'check',
187
+              onPress: this.hideMascotDialog,
188
+            },
189
+          }}
190
+          emotion={MASCOT_STYLE.WINK}
191
+        />
192
+      </View>
193
+    );
194
+  }
191 195
 }
192 196
 
193 197
 export default withTheme(EquipmentListScreen);

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

@@ -1,441 +1,441 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Button, Caption, Card, Headline, Subheading, withTheme} from 'react-native-paper';
5
-import {StackNavigationProp} from "@react-navigation/stack";
6
-import type {CustomTheme} from "../../../managers/ThemeManager";
7
-import type {Device} from "./EquipmentListScreen";
8
-import {BackHandler, View} from "react-native";
9
-import * as Animatable from "react-native-animatable";
10
-import i18n from "i18n-js";
11
-import {CalendarList} from "react-native-calendars";
12
-import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
13
-import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
14 4
 import {
15
-    generateMarkedDates,
16
-    getFirstEquipmentAvailability,
17
-    getISODate,
18
-    getRelativeDateString,
19
-    getValidRange,
20
-    isEquipmentAvailable
21
-} from "../../../utils/EquipmentBooking";
22
-import ConnectionManager from "../../../managers/ConnectionManager";
23
-import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
24
-
25
-type Props = {
26
-    navigation: StackNavigationProp,
27
-    route: {
28
-        params?: {
29
-            item?: Device,
30
-        },
5
+  Button,
6
+  Caption,
7
+  Card,
8
+  Headline,
9
+  Subheading,
10
+  withTheme,
11
+} from 'react-native-paper';
12
+import {StackNavigationProp} from '@react-navigation/stack';
13
+import {BackHandler, View} from 'react-native';
14
+import * as Animatable from 'react-native-animatable';
15
+import i18n from 'i18n-js';
16
+import {CalendarList} from 'react-native-calendars';
17
+import type {DeviceType} from './EquipmentListScreen';
18
+import type {CustomTheme} from '../../../managers/ThemeManager';
19
+import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog';
20
+import ErrorDialog from '../../../components/Dialogs/ErrorDialog';
21
+import {
22
+  generateMarkedDates,
23
+  getFirstEquipmentAvailability,
24
+  getISODate,
25
+  getRelativeDateString,
26
+  getValidRange,
27
+  isEquipmentAvailable,
28
+} from '../../../utils/EquipmentBooking';
29
+import ConnectionManager from '../../../managers/ConnectionManager';
30
+import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
31
+
32
+type PropsType = {
33
+  navigation: StackNavigationProp,
34
+  route: {
35
+    params?: {
36
+      item?: DeviceType,
31 37
     },
32
-    theme: CustomTheme,
33
-}
38
+  },
39
+  theme: CustomTheme,
40
+};
34 41
 
35
-type State = {
36
-    dialogVisible: boolean,
37
-    errorDialogVisible: boolean,
38
-    markedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } },
39
-    currentError: number,
40
-}
42
+export type MarkedDatesObjectType = {
43
+  [key: string]: {startingDay: boolean, endingDay: boolean, color: string},
44
+};
41 45
 
42
-class EquipmentRentScreen extends React.Component<Props, State> {
46
+type StateType = {
47
+  dialogVisible: boolean,
48
+  errorDialogVisible: boolean,
49
+  markedDates: MarkedDatesObjectType,
50
+  currentError: number,
51
+};
43 52
 
44
-    state = {
45
-        dialogVisible: false,
46
-        errorDialogVisible: false,
47
-        markedDates: {},
48
-        currentError: 0,
49
-    }
53
+class EquipmentRentScreen extends React.Component<PropsType, StateType> {
54
+  item: DeviceType | null;
50 55
 
51
-    item: Device | null;
52
-    bookedDates: Array<string>;
56
+  bookedDates: Array<string>;
53 57
 
54
-    bookRef: { current: null | Animatable.View }
55
-    canBookEquipment: boolean;
58
+  bookRef: {current: null | Animatable.View};
56 59
 
57
-    lockedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } }
60
+  canBookEquipment: boolean;
58 61
 
59
-    constructor(props: Props) {
60
-        super(props);
61
-        this.resetSelection();
62
-        this.bookRef = React.createRef();
63
-        this.canBookEquipment = false;
64
-        this.bookedDates = [];
65
-        if (this.props.route.params != null) {
66
-            if (this.props.route.params.item != null)
67
-                this.item = this.props.route.params.item;
68
-            else
69
-                this.item = null;
70
-        }
71
-        const item = this.item;
72
-        if (item != null) {
73
-            this.lockedDates = {};
74
-            for (let i = 0; i < item.booked_at.length; i++) {
75
-                const range = getValidRange(new Date(item.booked_at[i].begin), new Date(item.booked_at[i].end), null);
76
-                this.lockedDates = {
77
-                    ...this.lockedDates,
78
-                    ...generateMarkedDates(
79
-                        false,
80
-                        this.props.theme,
81
-                        range
82
-                    )
83
-                };
84
-            }
85
-        }
86
-
87
-    }
88
-
89
-    /**
90
-     * Captures focus and blur events to hook on android back button
91
-     */
92
-    componentDidMount() {
93
-        this.props.navigation.addListener(
94
-            'focus',
95
-            () =>
96
-                BackHandler.addEventListener(
97
-                    'hardwareBackPress',
98
-                    this.onBackButtonPressAndroid
99
-                )
100
-        );
101
-        this.props.navigation.addListener(
102
-            'blur',
103
-            () =>
104
-                BackHandler.removeEventListener(
105
-                    'hardwareBackPress',
106
-                    this.onBackButtonPressAndroid
107
-                )
108
-        );
109
-    }
62
+  lockedDates: {
63
+    [key: string]: {startingDay: boolean, endingDay: boolean, color: string},
64
+  };
110 65
 
111
-    /**
112
-     * Overrides default android back button behaviour to deselect date if any is selected.
113
-     *
114
-     * @return {boolean}
115
-     */
116
-    onBackButtonPressAndroid = () => {
117
-        if (this.bookedDates.length > 0) {
118
-            this.resetSelection();
119
-            this.updateMarkedSelection();
120
-            return true;
121
-        } else
122
-            return false;
66
+  constructor(props: PropsType) {
67
+    super(props);
68
+    this.state = {
69
+      dialogVisible: false,
70
+      errorDialogVisible: false,
71
+      markedDates: {},
72
+      currentError: 0,
123 73
     };
124
-
125
-    /**
126
-     * Selects a new date on the calendar.
127
-     * If both start and end dates are already selected, unselect all.
128
-     *
129
-     * @param day The day selected
130
-     */
131
-    selectNewDate = (day: { dateString: string, day: number, month: number, timestamp: number, year: number }) => {
132
-        const selected = new Date(day.dateString);
133
-        const start = this.getBookStartDate();
134
-
135
-        if (!(this.lockedDates.hasOwnProperty(day.dateString))) {
136
-            if (start === null) {
137
-                this.updateSelectionRange(selected, selected);
138
-                this.enableBooking();
139
-            } else if (start.getTime() === selected.getTime()) {
140
-                this.resetSelection();
141
-            } else if (this.bookedDates.length === 1) {
142
-                this.updateSelectionRange(start, selected);
143
-                this.enableBooking();
144
-            } else
145
-                this.resetSelection();
146
-            this.updateMarkedSelection();
147
-        }
148
-    }
149
-
150
-    updateSelectionRange(start: Date, end: Date) {
151
-        this.bookedDates = getValidRange(start, end, this.item);
152
-    }
153
-
154
-    updateMarkedSelection() {
155
-        this.setState({
156
-            markedDates: generateMarkedDates(
157
-                true,
158
-                this.props.theme,
159
-                this.bookedDates
160
-            ),
161
-        });
162
-    }
163
-
164
-    enableBooking() {
165
-        if (!this.canBookEquipment) {
166
-            this.showBookButton();
167
-            this.canBookEquipment = true;
168
-        }
169
-    }
170
-
171
-    resetSelection() {
172
-        if (this.canBookEquipment)
173
-            this.hideBookButton();
174
-        this.canBookEquipment = false;
175
-        this.bookedDates = [];
74
+    this.resetSelection();
75
+    this.bookRef = React.createRef();
76
+    this.canBookEquipment = false;
77
+    this.bookedDates = [];
78
+    if (props.route.params != null) {
79
+      if (props.route.params.item != null) this.item = props.route.params.item;
80
+      else this.item = null;
176 81
     }
177
-
178
-    /**
179
-     * Shows the book button by plying a fade animation
180
-     */
181
-    showBookButton() {
182
-        if (this.bookRef.current != null) {
183
-            this.bookRef.current.fadeInUp(500);
184
-        }
185
-    }
186
-
187
-    /**
188
-     * Hides the book button by plying a fade animation
189
-     */
190
-    hideBookButton() {
191
-        if (this.bookRef.current != null) {
192
-            this.bookRef.current.fadeOutDown(500);
193
-        }
194
-    }
195
-
196
-    showDialog = () => {
197
-        this.setState({dialogVisible: true});
198
-    }
199
-
200
-    showErrorDialog = (error: number) => {
201
-        this.setState({
202
-            errorDialogVisible: true,
203
-            currentError: error,
204
-        });
82
+    const {item} = this;
83
+    if (item != null) {
84
+      this.lockedDates = {};
85
+      item.booked_at.forEach((date: {begin: string, end: string}) => {
86
+        const range = getValidRange(
87
+          new Date(date.begin),
88
+          new Date(date.end),
89
+          null,
90
+        );
91
+        this.lockedDates = {
92
+          ...this.lockedDates,
93
+          ...generateMarkedDates(false, props.theme, range),
94
+        };
95
+      });
205 96
     }
206
-
207
-    onDialogDismiss = () => {
208
-        this.setState({dialogVisible: false});
97
+  }
98
+
99
+  /**
100
+   * Captures focus and blur events to hook on android back button
101
+   */
102
+  componentDidMount() {
103
+    const {navigation} = this.props;
104
+    navigation.addListener('focus', () => {
105
+      BackHandler.addEventListener(
106
+        'hardwareBackPress',
107
+        this.onBackButtonPressAndroid,
108
+      );
109
+    });
110
+    navigation.addListener('blur', () => {
111
+      BackHandler.removeEventListener(
112
+        'hardwareBackPress',
113
+        this.onBackButtonPressAndroid,
114
+      );
115
+    });
116
+  }
117
+
118
+  /**
119
+   * Overrides default android back button behaviour to deselect date if any is selected.
120
+   *
121
+   * @return {boolean}
122
+   */
123
+  onBackButtonPressAndroid = (): boolean => {
124
+    if (this.bookedDates.length > 0) {
125
+      this.resetSelection();
126
+      this.updateMarkedSelection();
127
+      return true;
209 128
     }
210
-
211
-    onErrorDialogDismiss = () => {
212
-        this.setState({errorDialogVisible: false});
129
+    return false;
130
+  };
131
+
132
+  onDialogDismiss = () => {
133
+    this.setState({dialogVisible: false});
134
+  };
135
+
136
+  onErrorDialogDismiss = () => {
137
+    this.setState({errorDialogVisible: false});
138
+  };
139
+
140
+  /**
141
+   * Sends the selected data to the server and waits for a response.
142
+   * If the request is a success, navigate to the recap screen.
143
+   * If it is an error, display the error to the user.
144
+   *
145
+   * @returns {Promise<void>}
146
+   */
147
+  onDialogAccept = (): Promise<void> => {
148
+    return new Promise((resolve: () => void) => {
149
+      const {item, props} = this;
150
+      const start = this.getBookStartDate();
151
+      const end = this.getBookEndDate();
152
+      if (item != null && start != null && end != null) {
153
+        ConnectionManager.getInstance()
154
+          .authenticatedRequest('location/booking', {
155
+            device: item.id,
156
+            begin: getISODate(start),
157
+            end: getISODate(end),
158
+          })
159
+          .then(() => {
160
+            this.onDialogDismiss();
161
+            props.navigation.replace('equipment-confirm', {
162
+              item: this.item,
163
+              dates: [getISODate(start), getISODate(end)],
164
+            });
165
+            resolve();
166
+          })
167
+          .catch((error: number) => {
168
+            this.onDialogDismiss();
169
+            this.showErrorDialog(error);
170
+            resolve();
171
+          });
172
+      } else {
173
+        this.onDialogDismiss();
174
+        resolve();
175
+      }
176
+    });
177
+  };
178
+
179
+  getBookStartDate(): Date | null {
180
+    return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
181
+  }
182
+
183
+  getBookEndDate(): Date | null {
184
+    const {length} = this.bookedDates;
185
+    return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
186
+  }
187
+
188
+  /**
189
+   * Selects a new date on the calendar.
190
+   * If both start and end dates are already selected, unselect all.
191
+   *
192
+   * @param day The day selected
193
+   */
194
+  selectNewDate = (day: {
195
+    dateString: string,
196
+    day: number,
197
+    month: number,
198
+    timestamp: number,
199
+    year: number,
200
+  }) => {
201
+    const selected = new Date(day.dateString);
202
+    const start = this.getBookStartDate();
203
+
204
+    if (!this.lockedDates[day.dateString] != null) {
205
+      if (start === null) {
206
+        this.updateSelectionRange(selected, selected);
207
+        this.enableBooking();
208
+      } else if (start.getTime() === selected.getTime()) {
209
+        this.resetSelection();
210
+      } else if (this.bookedDates.length === 1) {
211
+        this.updateSelectionRange(start, selected);
212
+        this.enableBooking();
213
+      } else this.resetSelection();
214
+      this.updateMarkedSelection();
213 215
     }
214
-
215
-    /**
216
-     * Sends the selected data to the server and waits for a response.
217
-     * If the request is a success, navigate to the recap screen.
218
-     * If it is an error, display the error to the user.
219
-     *
220
-     * @returns {Promise<R>}
221
-     */
222
-    onDialogAccept = () => {
223
-        return new Promise((resolve) => {
224
-            const item = this.item;
225
-            const start = this.getBookStartDate();
226
-            const end = this.getBookEndDate();
227
-            if (item != null && start != null && end != null) {
228
-                console.log({
229
-                    "device": item.id,
230
-                    "begin": getISODate(start),
231
-                    "end": getISODate(end),
232
-                })
233
-                ConnectionManager.getInstance().authenticatedRequest(
234
-                    "location/booking",
235
-                    {
236
-                        "device": item.id,
237
-                        "begin": getISODate(start),
238
-                        "end": getISODate(end),
239
-                    })
240
-                    .then(() => {
241
-                        this.onDialogDismiss();
242
-                        this.props.navigation.replace("equipment-confirm", {
243
-                            item: this.item,
244
-                            dates: [getISODate(start), getISODate(end)]
245
-                        });
246
-                        resolve();
247
-                    })
248
-                    .catch((error: number) => {
249
-                        this.onDialogDismiss();
250
-                        this.showErrorDialog(error);
251
-                        resolve();
252
-                    });
253
-            } else {
254
-                this.onDialogDismiss();
255
-                resolve();
256
-            }
257
-        });
216
+  };
217
+
218
+  showErrorDialog = (error: number) => {
219
+    this.setState({
220
+      errorDialogVisible: true,
221
+      currentError: error,
222
+    });
223
+  };
224
+
225
+  showDialog = () => {
226
+    this.setState({dialogVisible: true});
227
+  };
228
+
229
+  /**
230
+   * Shows the book button by plying a fade animation
231
+   */
232
+  showBookButton() {
233
+    if (this.bookRef.current != null) {
234
+      this.bookRef.current.fadeInUp(500);
258 235
     }
259
-
260
-    getBookStartDate() {
261
-        return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
236
+  }
237
+
238
+  /**
239
+   * Hides the book button by plying a fade animation
240
+   */
241
+  hideBookButton() {
242
+    if (this.bookRef.current != null) {
243
+      this.bookRef.current.fadeOutDown(500);
262 244
     }
245
+  }
263 246
 
264
-    getBookEndDate() {
265
-        const length = this.bookedDates.length;
266
-        return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
247
+  enableBooking() {
248
+    if (!this.canBookEquipment) {
249
+      this.showBookButton();
250
+      this.canBookEquipment = true;
267 251
     }
268
-
269
-    render() {
270
-        const item = this.item;
271
-        const start = this.getBookStartDate();
272
-        const end = this.getBookEndDate();
273
-
274
-        if (item != null) {
275
-            const isAvailable = isEquipmentAvailable(item);
276
-            const firstAvailability = getFirstEquipmentAvailability(item);
277
-            return (
252
+  }
253
+
254
+  resetSelection() {
255
+    if (this.canBookEquipment) this.hideBookButton();
256
+    this.canBookEquipment = false;
257
+    this.bookedDates = [];
258
+  }
259
+
260
+  updateSelectionRange(start: Date, end: Date) {
261
+    this.bookedDates = getValidRange(start, end, this.item);
262
+  }
263
+
264
+  updateMarkedSelection() {
265
+    const {theme} = this.props;
266
+    this.setState({
267
+      markedDates: generateMarkedDates(true, theme, this.bookedDates),
268
+    });
269
+  }
270
+
271
+  render(): React.Node {
272
+    const {item, props, state} = this;
273
+    const start = this.getBookStartDate();
274
+    const end = this.getBookEndDate();
275
+    let subHeadingText;
276
+    if (start == null) subHeadingText = i18n.t('screens.equipment.booking');
277
+    else if (end != null && start.getTime() !== end.getTime())
278
+      subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
279
+        begin: getRelativeDateString(start),
280
+        end: getRelativeDateString(end),
281
+      });
282
+    else
283
+      i18n.t('screens.equipment.bookingDay', {
284
+        date: getRelativeDateString(start),
285
+      });
286
+    if (item != null) {
287
+      const isAvailable = isEquipmentAvailable(item);
288
+      const firstAvailability = getFirstEquipmentAvailability(item);
289
+      return (
290
+        <View style={{flex: 1}}>
291
+          <CollapsibleScrollView>
292
+            <Card style={{margin: 5}}>
293
+              <Card.Content>
278 294
                 <View style={{flex: 1}}>
279
-                    <CollapsibleScrollView>
280
-                        <Card style={{margin: 5}}>
281
-                            <Card.Content>
282
-                                <View style={{flex: 1}}>
283
-                                    <View style={{
284
-                                        marginLeft: "auto",
285
-                                        marginRight: "auto",
286
-                                        flexDirection: "row",
287
-                                        flexWrap: "wrap",
288
-                                    }}>
289
-                                        <Headline style={{textAlign: "center"}}>
290
-                                            {item.name}
291
-                                        </Headline>
292
-                                        <Caption style={{
293
-                                            textAlign: "center",
294
-                                            lineHeight: 35,
295
-                                            marginLeft: 10,
296
-                                        }}>
297
-                                            ({i18n.t('screens.equipment.bail', {cost: item.caution})})
298
-                                        </Caption>
299
-                                    </View>
300
-                                </View>
301
-
302
-                                <Button
303
-                                    icon={isAvailable ? "check-circle-outline" : "update"}
304
-                                    color={isAvailable ? this.props.theme.colors.success : this.props.theme.colors.primary}
305
-                                    mode="text"
306
-                                >
307
-                                    {i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)})}
308
-                                </Button>
309
-                                <Subheading style={{
310
-                                    textAlign: "center",
311
-                                    marginBottom: 10,
312
-                                    minHeight: 50
313
-                                }}>
314
-                                    {
315
-                                        start == null
316
-                                            ? i18n.t('screens.equipment.booking')
317
-                                            : end != null && start.getTime() !== end.getTime()
318
-                                            ? i18n.t('screens.equipment.bookingPeriod', {
319
-                                                begin: getRelativeDateString(start),
320
-                                                end: getRelativeDateString(end)
321
-                                            })
322
-                                            : i18n.t('screens.equipment.bookingDay', {
323
-                                                date: getRelativeDateString(start)
324
-                                            })
325
-                                    }
326
-
327
-                                </Subheading>
328
-                            </Card.Content>
329
-                        </Card>
330
-                        <CalendarList
331
-                            // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
332
-                            minDate={new Date()}
333
-                            // Max amount of months allowed to scroll to the past. Default = 50
334
-                            pastScrollRange={0}
335
-                            // Max amount of months allowed to scroll to the future. Default = 50
336
-                            futureScrollRange={3}
337
-                            // Enable horizontal scrolling, default = false
338
-                            horizontal={true}
339
-                            // Enable paging on horizontal, default = false
340
-                            pagingEnabled={true}
341
-                            // Handler which gets executed on day press. Default = undefined
342
-                            onDayPress={this.selectNewDate}
343
-                            // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
344
-                            firstDay={1}
345
-                            // Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates
346
-                            disableAllTouchEventsForDisabledDays={true}
347
-                            // Hide month navigation arrows.
348
-                            hideArrows={false}
349
-                            // Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
350
-                            markingType={'period'}
351
-                            markedDates={{...this.lockedDates, ...this.state.markedDates}}
352
-
353
-                            theme={{
354
-                                backgroundColor: this.props.theme.colors.agendaBackgroundColor,
355
-                                calendarBackground: this.props.theme.colors.background,
356
-                                textSectionTitleColor: this.props.theme.colors.agendaDayTextColor,
357
-                                selectedDayBackgroundColor: this.props.theme.colors.primary,
358
-                                selectedDayTextColor: '#ffffff',
359
-                                todayTextColor: this.props.theme.colors.text,
360
-                                dayTextColor: this.props.theme.colors.text,
361
-                                textDisabledColor: this.props.theme.colors.agendaDayTextColor,
362
-                                dotColor: this.props.theme.colors.primary,
363
-                                selectedDotColor: '#ffffff',
364
-                                arrowColor: this.props.theme.colors.primary,
365
-                                monthTextColor: this.props.theme.colors.text,
366
-                                indicatorColor: this.props.theme.colors.primary,
367
-                                textDayFontFamily: 'monospace',
368
-                                textMonthFontFamily: 'monospace',
369
-                                textDayHeaderFontFamily: 'monospace',
370
-                                textDayFontWeight: '300',
371
-                                textMonthFontWeight: 'bold',
372
-                                textDayHeaderFontWeight: '300',
373
-                                textDayFontSize: 16,
374
-                                textMonthFontSize: 16,
375
-                                textDayHeaderFontSize: 16,
376
-                                'stylesheet.day.period': {
377
-                                    base: {
378
-                                        overflow: 'hidden',
379
-                                        height: 34,
380
-                                        width: 34,
381
-                                        alignItems: 'center',
382
-
383
-                                    }
384
-                                }
385
-                            }}
386
-                            style={{marginBottom: 50}}
387
-                        />
388
-                    </CollapsibleScrollView>
389
-                    <LoadingConfirmDialog
390
-                        visible={this.state.dialogVisible}
391
-                        onDismiss={this.onDialogDismiss}
392
-                        onAccept={this.onDialogAccept}
393
-                        title={i18n.t('screens.equipment.dialogTitle')}
394
-                        titleLoading={i18n.t('screens.equipment.dialogTitleLoading')}
395
-                        message={i18n.t('screens.equipment.dialogMessage')}
396
-                    />
397
-
398
-                    <ErrorDialog
399
-                        visible={this.state.errorDialogVisible}
400
-                        onDismiss={this.onErrorDialogDismiss}
401
-                        errorCode={this.state.currentError}
402
-                    />
403
-                    <Animatable.View
404
-                        ref={this.bookRef}
405
-                        style={{
406
-                            position: "absolute",
407
-                            bottom: 0,
408
-                            left: 0,
409
-                            width: "100%",
410
-                            flex: 1,
411
-                            transform: [
412
-                                {translateY: 100},
413
-                            ]
414
-                        }}>
415
-                        <Button
416
-                            icon="bookmark-check"
417
-                            mode="contained"
418
-                            onPress={this.showDialog}
419
-                            style={{
420
-                                width: "80%",
421
-                                flex: 1,
422
-                                marginLeft: "auto",
423
-                                marginRight: "auto",
424
-                                marginBottom: 20,
425
-                                borderRadius: 10
426
-                            }}
427
-                        >
428
-                            {i18n.t('screens.equipment.bookButton')}
429
-                        </Button>
430
-                    </Animatable.View>
431
-
295
+                  <View
296
+                    style={{
297
+                      marginLeft: 'auto',
298
+                      marginRight: 'auto',
299
+                      flexDirection: 'row',
300
+                      flexWrap: 'wrap',
301
+                    }}>
302
+                    <Headline style={{textAlign: 'center'}}>
303
+                      {item.name}
304
+                    </Headline>
305
+                    <Caption
306
+                      style={{
307
+                        textAlign: 'center',
308
+                        lineHeight: 35,
309
+                        marginLeft: 10,
310
+                      }}>
311
+                      ({i18n.t('screens.equipment.bail', {cost: item.caution})})
312
+                    </Caption>
313
+                  </View>
432 314
                 </View>
433 315
 
434
-            )
435
-        } else
436
-            return <View/>;
316
+                <Button
317
+                  icon={isAvailable ? 'check-circle-outline' : 'update'}
318
+                  color={
319
+                    isAvailable
320
+                      ? props.theme.colors.success
321
+                      : props.theme.colors.primary
322
+                  }
323
+                  mode="text">
324
+                  {i18n.t('screens.equipment.available', {
325
+                    date: getRelativeDateString(firstAvailability),
326
+                  })}
327
+                </Button>
328
+                <Subheading
329
+                  style={{
330
+                    textAlign: 'center',
331
+                    marginBottom: 10,
332
+                    minHeight: 50,
333
+                  }}>
334
+                  {subHeadingText}
335
+                </Subheading>
336
+              </Card.Content>
337
+            </Card>
338
+            <CalendarList
339
+              // Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
340
+              minDate={new Date()}
341
+              // Max amount of months allowed to scroll to the past. Default = 50
342
+              pastScrollRange={0}
343
+              // Max amount of months allowed to scroll to the future. Default = 50
344
+              futureScrollRange={3}
345
+              // Enable horizontal scrolling, default = false
346
+              horizontal
347
+              // Enable paging on horizontal, default = false
348
+              pagingEnabled
349
+              // Handler which gets executed on day press. Default = undefined
350
+              onDayPress={this.selectNewDate}
351
+              // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
352
+              firstDay={1}
353
+              // Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates
354
+              disableAllTouchEventsForDisabledDays
355
+              // Hide month navigation arrows.
356
+              hideArrows={false}
357
+              // Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
358
+              markingType="period"
359
+              markedDates={{...this.lockedDates, ...state.markedDates}}
360
+              theme={{
361
+                backgroundColor: props.theme.colors.agendaBackgroundColor,
362
+                calendarBackground: props.theme.colors.background,
363
+                textSectionTitleColor: props.theme.colors.agendaDayTextColor,
364
+                selectedDayBackgroundColor: props.theme.colors.primary,
365
+                selectedDayTextColor: '#ffffff',
366
+                todayTextColor: props.theme.colors.text,
367
+                dayTextColor: props.theme.colors.text,
368
+                textDisabledColor: props.theme.colors.agendaDayTextColor,
369
+                dotColor: props.theme.colors.primary,
370
+                selectedDotColor: '#ffffff',
371
+                arrowColor: props.theme.colors.primary,
372
+                monthTextColor: props.theme.colors.text,
373
+                indicatorColor: props.theme.colors.primary,
374
+                textDayFontFamily: 'monospace',
375
+                textMonthFontFamily: 'monospace',
376
+                textDayHeaderFontFamily: 'monospace',
377
+                textDayFontWeight: '300',
378
+                textMonthFontWeight: 'bold',
379
+                textDayHeaderFontWeight: '300',
380
+                textDayFontSize: 16,
381
+                textMonthFontSize: 16,
382
+                textDayHeaderFontSize: 16,
383
+                'stylesheet.day.period': {
384
+                  base: {
385
+                    overflow: 'hidden',
386
+                    height: 34,
387
+                    width: 34,
388
+                    alignItems: 'center',
389
+                  },
390
+                },
391
+              }}
392
+              style={{marginBottom: 50}}
393
+            />
394
+          </CollapsibleScrollView>
395
+          <LoadingConfirmDialog
396
+            visible={state.dialogVisible}
397
+            onDismiss={this.onDialogDismiss}
398
+            onAccept={this.onDialogAccept}
399
+            title={i18n.t('screens.equipment.dialogTitle')}
400
+            titleLoading={i18n.t('screens.equipment.dialogTitleLoading')}
401
+            message={i18n.t('screens.equipment.dialogMessage')}
402
+          />
403
+
404
+          <ErrorDialog
405
+            visible={state.errorDialogVisible}
406
+            onDismiss={this.onErrorDialogDismiss}
407
+            errorCode={state.currentError}
408
+          />
409
+          <Animatable.View
410
+            ref={this.bookRef}
411
+            style={{
412
+              position: 'absolute',
413
+              bottom: 0,
414
+              left: 0,
415
+              width: '100%',
416
+              flex: 1,
417
+              transform: [{translateY: 100}],
418
+            }}>
419
+            <Button
420
+              icon="bookmark-check"
421
+              mode="contained"
422
+              onPress={this.showDialog}
423
+              style={{
424
+                width: '80%',
425
+                flex: 1,
426
+                marginLeft: 'auto',
427
+                marginRight: 'auto',
428
+                marginBottom: 20,
429
+                borderRadius: 10,
430
+              }}>
431
+              {i18n.t('screens.equipment.bookButton')}
432
+            </Button>
433
+          </Animatable.View>
434
+        </View>
435
+      );
437 436
     }
438
-
437
+    return null;
438
+  }
439 439
 }
440 440
 
441 441
 export default withTheme(EquipmentRentScreen);

+ 111
- 105
src/utils/EquipmentBooking.js View File

@@ -1,19 +1,20 @@
1 1
 // @flow
2 2
 
3
-import type {Device} from "../screens/Amicale/Equipment/EquipmentListScreen";
4
-import i18n from "i18n-js";
5
-import DateManager from "../managers/DateManager";
6
-import type {CustomTheme} from "../managers/ThemeManager";
3
+import i18n from 'i18n-js';
4
+import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen';
5
+import DateManager from '../managers/DateManager';
6
+import type {CustomTheme} from '../managers/ThemeManager';
7
+import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen';
7 8
 
8 9
 /**
9 10
  * Gets the current day at midnight
10 11
  *
11 12
  * @returns {Date}
12 13
  */
13
-export function getCurrentDay() {
14
-    let today = new Date(Date.now());
15
-    today.setUTCHours(0, 0, 0, 0);
16
-    return today;
14
+export function getCurrentDay(): Date {
15
+  const today = new Date(Date.now());
16
+  today.setUTCHours(0, 0, 0, 0);
17
+  return today;
17 18
 }
18 19
 
19 20
 /**
@@ -22,8 +23,8 @@ export function getCurrentDay() {
22 23
  * @param date The date to recover the ISO format from
23 24
  * @returns {*}
24 25
  */
25
-export function getISODate(date: Date) {
26
-    return date.toISOString().split("T")[0];
26
+export function getISODate(date: Date): string {
27
+  return date.toISOString().split('T')[0];
27 28
 }
28 29
 
29 30
 /**
@@ -32,18 +33,16 @@ export function getISODate(date: Date) {
32 33
  * @param item
33 34
  * @returns {boolean}
34 35
  */
35
-export function isEquipmentAvailable(item: Device) {
36
-    let isAvailable = true;
37
-    const today = getCurrentDay();
38
-    const dates = item.booked_at;
39
-    for (let i = 0; i < dates.length; i++) {
40
-        const start = new Date(dates[i].begin);
41
-        const end = new Date(dates[i].end);
42
-        isAvailable = today < start || today > end;
43
-        if (!isAvailable)
44
-            break;
45
-    }
46
-    return isAvailable;
36
+export function isEquipmentAvailable(item: DeviceType): boolean {
37
+  let isAvailable = true;
38
+  const today = getCurrentDay();
39
+  const dates = item.booked_at;
40
+  dates.forEach((date: {begin: string, end: string}) => {
41
+    const start = new Date(date.begin);
42
+    const end = new Date(date.end);
43
+    if (!(today < start || today > end)) isAvailable = false;
44
+  });
45
+  return isAvailable;
47 46
 }
48 47
 
49 48
 /**
@@ -52,17 +51,16 @@ export function isEquipmentAvailable(item: Device) {
52 51
  * @param item
53 52
  * @returns {Date}
54 53
  */
55
-export function getFirstEquipmentAvailability(item: Device) {
56
-    let firstAvailability = getCurrentDay();
57
-    const dates = item.booked_at;
58
-    for (let i = 0; i < dates.length; i++) {
59
-        const start = new Date(dates[i].begin);
60
-        let end = new Date(dates[i].end);
61
-        end.setDate(end.getDate() + 1);
62
-        if (firstAvailability >= start)
63
-            firstAvailability = end;
64
-    }
65
-    return firstAvailability;
54
+export function getFirstEquipmentAvailability(item: DeviceType): Date {
55
+  let firstAvailability = getCurrentDay();
56
+  const dates = item.booked_at;
57
+  dates.forEach((date: {begin: string, end: string}) => {
58
+    const start = new Date(date.begin);
59
+    const end = new Date(date.end);
60
+    end.setDate(end.getDate() + 1);
61
+    if (firstAvailability >= start) firstAvailability = end;
62
+  });
63
+  return firstAvailability;
66 64
 }
67 65
 
68 66
 /**
@@ -70,31 +68,31 @@ export function getFirstEquipmentAvailability(item: Device) {
70 68
  *
71 69
  * @param date The date to translate
72 70
  */
73
-export function getRelativeDateString(date: Date) {
74
-    const today = getCurrentDay();
75
-    const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
76
-    const monthDelta = date.getUTCMonth() - today.getUTCMonth();
77
-    const dayDelta = date.getUTCDate() - today.getUTCDate();
78
-    let translatedString = i18n.t('screens.equipment.today');
79
-    if (yearDelta > 0)
80
-        translatedString = i18n.t('screens.equipment.otherYear', {
81
-            date: date.getDate(),
82
-            month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
83
-            year: date.getFullYear()
84
-        });
85
-    else if (monthDelta > 0)
86
-        translatedString = i18n.t('screens.equipment.otherMonth', {
87
-            date: date.getDate(),
88
-            month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
89
-        });
90
-    else if (dayDelta > 1)
91
-        translatedString = i18n.t('screens.equipment.thisMonth', {
92
-            date: date.getDate(),
93
-        });
94
-    else if (dayDelta === 1)
95
-        translatedString = i18n.t('screens.equipment.tomorrow');
71
+export function getRelativeDateString(date: Date): string {
72
+  const today = getCurrentDay();
73
+  const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
74
+  const monthDelta = date.getUTCMonth() - today.getUTCMonth();
75
+  const dayDelta = date.getUTCDate() - today.getUTCDate();
76
+  let translatedString = i18n.t('screens.equipment.today');
77
+  if (yearDelta > 0)
78
+    translatedString = i18n.t('screens.equipment.otherYear', {
79
+      date: date.getDate(),
80
+      month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
81
+      year: date.getFullYear(),
82
+    });
83
+  else if (monthDelta > 0)
84
+    translatedString = i18n.t('screens.equipment.otherMonth', {
85
+      date: date.getDate(),
86
+      month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
87
+    });
88
+  else if (dayDelta > 1)
89
+    translatedString = i18n.t('screens.equipment.thisMonth', {
90
+      date: date.getDate(),
91
+    });
92
+  else if (dayDelta === 1)
93
+    translatedString = i18n.t('screens.equipment.tomorrow');
96 94
 
97
-    return translatedString;
95
+  return translatedString;
98 96
 }
99 97
 
100 98
 /**
@@ -111,41 +109,45 @@ export function getRelativeDateString(date: Date) {
111 109
  * @param item Item containing booked dates to look for
112 110
  * @returns {[string]}
113 111
  */
114
-export function getValidRange(start: Date, end: Date, item: Device | null) {
115
-    let direction = start <= end ? 1 : -1;
116
-    let limit = new Date(end);
117
-    limit.setDate(limit.getDate() + direction); // Limit is excluded, but we want to include range end
118
-    if (item != null) {
119
-        if (direction === 1) {
120
-            for (let i = 0; i < item.booked_at.length; i++) {
121
-                const bookLimit = new Date(item.booked_at[i].begin);
122
-                if (start < bookLimit && limit > bookLimit) {
123
-                    limit = bookLimit;
124
-                    break;
125
-                }
126
-            }
127
-        } else {
128
-            for (let i = item.booked_at.length - 1; i >= 0; i--) {
129
-                const bookLimit = new Date(item.booked_at[i].end);
130
-                if (start > bookLimit && limit < bookLimit) {
131
-                    limit = bookLimit;
132
-                    break;
133
-                }
134
-            }
112
+export function getValidRange(
113
+  start: Date,
114
+  end: Date,
115
+  item: DeviceType | null,
116
+): Array<string> {
117
+  const direction = start <= end ? 1 : -1;
118
+  let limit = new Date(end);
119
+  limit.setDate(limit.getDate() + direction); // Limit is excluded, but we want to include range end
120
+  if (item != null) {
121
+    if (direction === 1) {
122
+      for (let i = 0; i < item.booked_at.length; i += 1) {
123
+        const bookLimit = new Date(item.booked_at[i].begin);
124
+        if (start < bookLimit && limit > bookLimit) {
125
+          limit = bookLimit;
126
+          break;
127
+        }
128
+      }
129
+    } else {
130
+      for (let i = item.booked_at.length - 1; i >= 0; i -= 1) {
131
+        const bookLimit = new Date(item.booked_at[i].end);
132
+        if (start > bookLimit && limit < bookLimit) {
133
+          limit = bookLimit;
134
+          break;
135 135
         }
136
+      }
136 137
     }
138
+  }
137 139
 
138
-
139
-    let validRange = [];
140
-    let date = new Date(start);
141
-    while ((direction === 1 && date < limit) || (direction === -1 && date > limit)) {
142
-        if (direction === 1)
143
-            validRange.push(getISODate(date));
144
-        else
145
-            validRange.unshift(getISODate(date));
146
-        date.setDate(date.getDate() + direction);
147
-    }
148
-    return validRange;
140
+  const validRange = [];
141
+  const date = new Date(start);
142
+  while (
143
+    (direction === 1 && date < limit) ||
144
+    (direction === -1 && date > limit)
145
+  ) {
146
+    if (direction === 1) validRange.push(getISODate(date));
147
+    else validRange.unshift(getISODate(date));
148
+    date.setDate(date.getDate() + direction);
149
+  }
150
+  return validRange;
149 151
 }
150 152
 
151 153
 /**
@@ -157,20 +159,24 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
157 159
  * @param range The range to mark dates for
158 160
  * @returns {{}}
159 161
  */
160
-export function generateMarkedDates(isSelection: boolean, theme: CustomTheme, range: Array<string>) {
161
-    let markedDates = {}
162
-    for (let i = 0; i < range.length; i++) {
163
-        const isStart = i === 0;
164
-        const isEnd = i === range.length - 1;
165
-        markedDates[range[i]] = {
166
-            startingDay: isStart,
167
-            endingDay: isEnd,
168
-            color: isSelection
169
-                ? isStart || isEnd
170
-                    ? theme.colors.primary
171
-                    : theme.colors.danger
172
-                : theme.colors.textDisabled
173
-        };
174
-    }
175
-    return markedDates;
162
+export function generateMarkedDates(
163
+  isSelection: boolean,
164
+  theme: CustomTheme,
165
+  range: Array<string>,
166
+): MarkedDatesObjectType {
167
+  const markedDates = {};
168
+  for (let i = 0; i < range.length; i += 1) {
169
+    const isStart = i === 0;
170
+    const isEnd = i === range.length - 1;
171
+    let color;
172
+    if (isSelection && (isStart || isEnd)) color = theme.colors.primary;
173
+    else if (isSelection) color = theme.colors.danger;
174
+    else color = theme.colors.textDisabled;
175
+    markedDates[range[i]] = {
176
+      startingDay: isStart,
177
+      endingDay: isEnd,
178
+      color,
179
+    };
180
+  }
181
+  return markedDates;
176 182
 }

Loading…
Cancel
Save