Browse Source

Improve Proximo components to match linter

Arnaud Vergnet 3 years ago
parent
commit
547af66977

+ 42
- 42
src/components/Lists/Proximo/ProximoListItem.js View File

@@ -2,48 +2,48 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {Avatar, List, Text, withTheme} from 'react-native-paper';
5
-import i18n from "i18n-js";
6
-
7
-type Props = {
8
-    onPress: Function,
9
-    color: string,
10
-    item: Object,
11
-    height: number,
12
-}
13
-
14
-class ProximoListItem extends React.Component<Props> {
15
-
16
-    colors: Object;
17
-
18
-    constructor(props) {
19
-        super(props);
20
-        this.colors = props.theme.colors;
21
-    }
22
-
23
-    shouldComponentUpdate() {
24
-        return false;
25
-    }
26
-
27
-    render() {
28
-        return (
29
-            <List.Item
30
-                title={this.props.item.name}
31
-                description={this.props.item.quantity + ' ' + i18n.t('screens.proximo.inStock')}
32
-                descriptionStyle={{color: this.props.color}}
33
-                onPress={this.props.onPress}
34
-                left={() => <Avatar.Image style={{backgroundColor: 'transparent'}} size={64}
35
-                                          source={{uri: this.props.item.image}}/>}
36
-                right={() =>
37
-                    <Text style={{fontWeight: "bold"}}>
38
-                        {this.props.item.price}€
39
-                    </Text>}
40
-                style={{
41
-                    height: this.props.height,
42
-                    justifyContent: 'center',
43
-                }}
44
-            />
45
-        );
46
-    }
5
+import i18n from 'i18n-js';
6
+import type {ProximoArticleType} from '../../../screens/Services/Proximo/ProximoMainScreen';
7
+
8
+type PropsType = {
9
+  onPress: () => void,
10
+  color: string,
11
+  item: ProximoArticleType,
12
+  height: number,
13
+};
14
+
15
+class ProximoListItem extends React.Component<PropsType> {
16
+  shouldComponentUpdate(): boolean {
17
+    return false;
18
+  }
19
+
20
+  render(): React.Node {
21
+    const {props} = this;
22
+    return (
23
+      <List.Item
24
+        title={props.item.name}
25
+        description={`${props.item.quantity} ${i18n.t(
26
+          'screens.proximo.inStock',
27
+        )}`}
28
+        descriptionStyle={{color: props.color}}
29
+        onPress={props.onPress}
30
+        left={(): React.Node => (
31
+          <Avatar.Image
32
+            style={{backgroundColor: 'transparent'}}
33
+            size={64}
34
+            source={{uri: props.item.image}}
35
+          />
36
+        )}
37
+        right={(): React.Node => (
38
+          <Text style={{fontWeight: 'bold'}}>{props.item.price}€</Text>
39
+        )}
40
+        style={{
41
+          height: props.height,
42
+          justifyContent: 'center',
43
+        }}
44
+      />
45
+    );
46
+  }
47 47
 }
48 48
 
49 49
 export default withTheme(ProximoListItem);

+ 233
- 217
src/components/Screens/WebSectionList.js View File

@@ -1,44 +1,55 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {ERROR_TYPE, readData} from "../../utils/WebData";
5
-import i18n from "i18n-js";
4
+import i18n from 'i18n-js';
6 5
 import {Snackbar} from 'react-native-paper';
7
-import {RefreshControl, View} from "react-native";
8
-import ErrorView from "./ErrorView";
9
-import BasicLoadingScreen from "./BasicLoadingScreen";
10
-import {withCollapsible} from "../../utils/withCollapsible";
6
+import {RefreshControl, View} from 'react-native';
11 7
 import * as Animatable from 'react-native-animatable';
12
-import CustomTabBar from "../Tabbar/CustomTabBar";
13
-import {Collapsible} from "react-navigation-collapsible";
14
-import {StackNavigationProp} from "@react-navigation/stack";
15
-import CollapsibleSectionList from "../Collapsible/CollapsibleSectionList";
8
+import {Collapsible} from 'react-navigation-collapsible';
9
+import {StackNavigationProp} from '@react-navigation/stack';
10
+import ErrorView from './ErrorView';
11
+import BasicLoadingScreen from './BasicLoadingScreen';
12
+import {withCollapsible} from '../../utils/withCollapsible';
13
+import CustomTabBar from '../Tabbar/CustomTabBar';
14
+import {ERROR_TYPE, readData} from '../../utils/WebData';
15
+import CollapsibleSectionList from '../Collapsible/CollapsibleSectionList';
16
+import type {ApiGenericDataType} from '../../utils/WebData';
16 17
 
17
-type Props = {
18
-    navigation: StackNavigationProp,
19
-    fetchUrl: string,
20
-    autoRefreshTime: number,
21
-    refreshOnFocus: boolean,
22
-    renderItem: (data: { [key: string]: any }) => React.Node,
23
-    createDataset: (data: { [key: string]: any } | null, isLoading?: boolean) => Array<Object>,
24
-    onScroll: (event: SyntheticEvent<EventTarget>) => void,
25
-    collapsibleStack: Collapsible,
18
+export type SectionListDataType<T> = Array<{
19
+  title: string,
20
+  data: Array<T>,
21
+  keyExtractor?: (T) => string,
22
+}>;
26 23
 
27
-    showError: boolean,
28
-    itemHeight?: number,
29
-    updateData?: number,
30
-    renderListHeaderComponent?: (data: { [key: string]: any } | null) => React.Node,
31
-    renderSectionHeader?: (data: { section: { [key: string]: any } }, isLoading?: boolean) => React.Node,
32
-    stickyHeader?: boolean,
33
-}
24
+type PropsType<T> = {
25
+  navigation: StackNavigationProp,
26
+  fetchUrl: string,
27
+  autoRefreshTime: number,
28
+  refreshOnFocus: boolean,
29
+  renderItem: (data: {item: T}) => React.Node,
30
+  createDataset: (
31
+    data: ApiGenericDataType | null,
32
+    isLoading?: boolean,
33
+  ) => SectionListDataType<T>,
34
+  onScroll: (event: SyntheticEvent<EventTarget>) => void,
35
+  collapsibleStack: Collapsible,
34 36
 
35
-type State = {
36
-    refreshing: boolean,
37
-    firstLoading: boolean,
38
-    fetchedData: { [key: string]: any } | null,
39
-    snackbarVisible: boolean
37
+  showError?: boolean,
38
+  itemHeight?: number | null,
39
+  updateData?: number,
40
+  renderListHeaderComponent?: (data: ApiGenericDataType | null) => React.Node,
41
+  renderSectionHeader?: (
42
+    data: {section: {title: string}},
43
+    isLoading?: boolean,
44
+  ) => React.Node,
45
+  stickyHeader?: boolean,
40 46
 };
41 47
 
48
+type StateType = {
49
+  refreshing: boolean,
50
+  fetchedData: ApiGenericDataType | null,
51
+  snackbarVisible: boolean,
52
+};
42 53
 
43 54
 const MIN_REFRESH_TIME = 5 * 1000;
44 55
 
@@ -48,211 +59,216 @@ const MIN_REFRESH_TIME = 5 * 1000;
48 59
  * This is a pure component, meaning it will only update if a shallow comparison of state and props is different.
49 60
  * To force the component to update, change the value of updateData.
50 61
  */
51
-class WebSectionList extends React.PureComponent<Props, State> {
62
+class WebSectionList<T> extends React.PureComponent<PropsType<T>, StateType> {
63
+  static defaultProps = {
64
+    showError: true,
65
+    itemHeight: null,
66
+    updateData: 0,
67
+    renderListHeaderComponent: (): React.Node => null,
68
+    renderSectionHeader: (): React.Node => null,
69
+    stickyHeader: false,
70
+  };
52 71
 
53
-    static defaultProps = {
54
-        stickyHeader: false,
55
-        updateData: 0,
56
-        showError: true,
57
-    };
72
+  refreshInterval: IntervalID;
58 73
 
59
-    refreshInterval: IntervalID;
60
-    lastRefresh: Date | null;
74
+  lastRefresh: Date | null;
61 75
 
62
-    state = {
63
-        refreshing: false,
64
-        firstLoading: true,
65
-        fetchedData: null,
66
-        snackbarVisible: false
76
+  constructor() {
77
+    super();
78
+    this.state = {
79
+      refreshing: false,
80
+      fetchedData: null,
81
+      snackbarVisible: false,
67 82
     };
83
+  }
68 84
 
69
-    /**
70
-     * Registers react navigation events on first screen load.
71
-     * Allows to detect when the screen is focused
72
-     */
73
-    componentDidMount() {
74
-        this.props.navigation.addListener('focus', this.onScreenFocus);
75
-        this.props.navigation.addListener('blur', this.onScreenBlur);
76
-        this.lastRefresh = null;
77
-        this.onRefresh();
78
-    }
85
+  /**
86
+   * Registers react navigation events on first screen load.
87
+   * Allows to detect when the screen is focused
88
+   */
89
+  componentDidMount() {
90
+    const {navigation} = this.props;
91
+    navigation.addListener('focus', this.onScreenFocus);
92
+    navigation.addListener('blur', this.onScreenBlur);
93
+    this.lastRefresh = null;
94
+    this.onRefresh();
95
+  }
79 96
 
80
-    /**
81
-     * Refreshes data when focusing the screen and setup a refresh interval if asked to
82
-     */
83
-    onScreenFocus = () => {
84
-        if (this.props.refreshOnFocus && this.lastRefresh)
85
-            this.onRefresh();
86
-        if (this.props.autoRefreshTime > 0)
87
-            this.refreshInterval = setInterval(this.onRefresh, this.props.autoRefreshTime)
88
-    }
97
+  /**
98
+   * Refreshes data when focusing the screen and setup a refresh interval if asked to
99
+   */
100
+  onScreenFocus = () => {
101
+    const {props} = this;
102
+    if (props.refreshOnFocus && this.lastRefresh) this.onRefresh();
103
+    if (props.autoRefreshTime > 0)
104
+      this.refreshInterval = setInterval(this.onRefresh, props.autoRefreshTime);
105
+  };
89 106
 
90
-    /**
91
-     * Removes any interval on un-focus
92
-     */
93
-    onScreenBlur = () => {
94
-        clearInterval(this.refreshInterval);
95
-    }
107
+  /**
108
+   * Removes any interval on un-focus
109
+   */
110
+  onScreenBlur = () => {
111
+    clearInterval(this.refreshInterval);
112
+  };
96 113
 
114
+  /**
115
+   * Callback used when fetch is successful.
116
+   * It will update the displayed data and stop the refresh animation
117
+   *
118
+   * @param fetchedData The newly fetched data
119
+   */
120
+  onFetchSuccess = (fetchedData: ApiGenericDataType) => {
121
+    this.setState({
122
+      fetchedData,
123
+      refreshing: false,
124
+    });
125
+    this.lastRefresh = new Date();
126
+  };
97 127
 
98
-    /**
99
-     * Callback used when fetch is successful.
100
-     * It will update the displayed data and stop the refresh animation
101
-     *
102
-     * @param fetchedData The newly fetched data
103
-     */
104
-    onFetchSuccess = (fetchedData: { [key: string]: any }) => {
105
-        this.setState({
106
-            fetchedData: fetchedData,
107
-            refreshing: false,
108
-            firstLoading: false
109
-        });
110
-        this.lastRefresh = new Date();
111
-    };
128
+  /**
129
+   * Callback used when fetch encountered an error.
130
+   * It will reset the displayed data and show an error.
131
+   */
132
+  onFetchError = () => {
133
+    this.setState({
134
+      fetchedData: null,
135
+      refreshing: false,
136
+    });
137
+    this.showSnackBar();
138
+  };
112 139
 
113
-    /**
114
-     * Callback used when fetch encountered an error.
115
-     * It will reset the displayed data and show an error.
116
-     */
117
-    onFetchError = () => {
118
-        this.setState({
119
-            fetchedData: null,
120
-            refreshing: false,
121
-            firstLoading: false
122
-        });
123
-        this.showSnackBar();
124
-    };
125
-
126
-    /**
127
-     * Refreshes data and shows an animations while doing it
128
-     */
129
-    onRefresh = () => {
130
-        let canRefresh;
131
-        if (this.lastRefresh != null) {
132
-            const last = this.lastRefresh;
133
-            canRefresh = (new Date().getTime() - last.getTime()) > MIN_REFRESH_TIME;
134
-        } else
135
-            canRefresh = true;
136
-        if (canRefresh) {
137
-            this.setState({refreshing: true});
138
-            readData(this.props.fetchUrl)
139
-                .then(this.onFetchSuccess)
140
-                .catch(this.onFetchError);
141
-        }
142
-    };
140
+  /**
141
+   * Refreshes data and shows an animations while doing it
142
+   */
143
+  onRefresh = () => {
144
+    const {fetchUrl} = this.props;
145
+    let canRefresh;
146
+    if (this.lastRefresh != null) {
147
+      const last = this.lastRefresh;
148
+      canRefresh = new Date().getTime() - last.getTime() > MIN_REFRESH_TIME;
149
+    } else canRefresh = true;
150
+    if (canRefresh) {
151
+      this.setState({refreshing: true});
152
+      readData(fetchUrl).then(this.onFetchSuccess).catch(this.onFetchError);
153
+    }
154
+  };
143 155
 
144
-    /**
145
-     * Shows the error popup
146
-     */
147
-    showSnackBar = () => this.setState({snackbarVisible: true});
156
+  /**
157
+   * Shows the error popup
158
+   */
159
+  showSnackBar = () => {
160
+    this.setState({snackbarVisible: true});
161
+  };
148 162
 
149
-    /**
150
-     * Hides the error popup
151
-     */
152
-    hideSnackBar = () => this.setState({snackbarVisible: false});
163
+  /**
164
+   * Hides the error popup
165
+   */
166
+  hideSnackBar = () => {
167
+    this.setState({snackbarVisible: false});
168
+  };
153 169
 
154
-    itemLayout = (data: { [key: string]: any }, index: number) => {
155
-        const height = this.props.itemHeight;
156
-        if (height == null)
157
-            return undefined;
158
-        return {
159
-            length: height,
160
-            offset: height * index,
161
-            index
162
-        }
170
+  getItemLayout = (
171
+    data: T,
172
+    index: number,
173
+  ): {length: number, offset: number, index: number} | null => {
174
+    const {itemHeight} = this.props;
175
+    if (itemHeight == null) return null;
176
+    return {
177
+      length: itemHeight,
178
+      offset: itemHeight * index,
179
+      index,
163 180
     };
181
+  };
164 182
 
165
-    renderSectionHeader = (data: { section: { [key: string]: any } }) => {
166
-        if (this.props.renderSectionHeader != null) {
167
-            return (
168
-                <Animatable.View
169
-                    animation={"fadeInUp"}
170
-                    duration={500}
171
-                    useNativeDriver
172
-                >
173
-                    {this.props.renderSectionHeader(data, this.state.refreshing)}
174
-                </Animatable.View>
175
-            );
176
-        } else
177
-            return null;
183
+  getRenderSectionHeader = (data: {section: {title: string}}): React.Node => {
184
+    const {renderSectionHeader} = this.props;
185
+    const {refreshing} = this.state;
186
+    if (renderSectionHeader != null) {
187
+      return (
188
+        <Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
189
+          {renderSectionHeader(data, refreshing)}
190
+        </Animatable.View>
191
+      );
178 192
     }
193
+    return null;
194
+  };
179 195
 
180
-    renderItem = (data: {
181
-        item: { [key: string]: any },
182
-        index: number,
183
-        section: { [key: string]: any },
184
-        separators: { [key: string]: any },
185
-    }) => {
186
-        return (
187
-            <Animatable.View
188
-                animation={"fadeInUp"}
189
-                duration={500}
190
-                useNativeDriver
191
-            >
192
-                {this.props.renderItem(data)}
193
-            </Animatable.View>
194
-        );
195
-    }
196
+  getRenderItem = (data: {item: T}): React.Node => {
197
+    const {renderItem} = this.props;
198
+    return (
199
+      <Animatable.View animation="fadeInUp" duration={500} useNativeDriver>
200
+        {renderItem(data)}
201
+      </Animatable.View>
202
+    );
203
+  };
196 204
 
197
-    onScroll = (event: SyntheticEvent<EventTarget>) => {
198
-        if (this.props.onScroll)
199
-            this.props.onScroll(event);
200
-    }
205
+  onScroll = (event: SyntheticEvent<EventTarget>) => {
206
+    const {onScroll} = this.props;
207
+    if (onScroll != null) onScroll(event);
208
+  };
201 209
 
202
-    render() {
203
-        let dataset = [];
204
-        if (this.state.fetchedData != null || (this.state.fetchedData == null && !this.props.showError)) {
205
-            dataset = this.props.createDataset(this.state.fetchedData, this.state.refreshing);
206
-        }
207
-        const {containerPaddingTop} = this.props.collapsibleStack;
208
-        return (
209
-            <View>
210
-                <CollapsibleSectionList
211
-                    sections={dataset}
212
-                    extraData={this.props.updateData}
213
-                    refreshControl={
214
-                        <RefreshControl
215
-                            progressViewOffset={containerPaddingTop}
216
-                            refreshing={this.state.refreshing}
217
-                            onRefresh={this.onRefresh}
218
-                        />
219
-                    }
220
-                    renderSectionHeader={this.renderSectionHeader}
221
-                    renderItem={this.renderItem}
222
-                    stickySectionHeadersEnabled={this.props.stickyHeader}
223
-                    style={{minHeight: '100%'}}
224
-                    ListHeaderComponent={this.props.renderListHeaderComponent != null
225
-                        ? this.props.renderListHeaderComponent(this.state.fetchedData)
226
-                        : null}
227
-                    ListEmptyComponent={this.state.refreshing
228
-                        ? <BasicLoadingScreen/>
229
-                        : <ErrorView
230
-                            {...this.props}
231
-                            errorCode={ERROR_TYPE.CONNECTION_ERROR}
232
-                            onRefresh={this.onRefresh}/>
233
-                    }
234
-                    getItemLayout={this.props.itemHeight != null ? this.itemLayout : undefined}
235
-                    onScroll={this.onScroll}
236
-                    hasTab={true}
237
-                />
238
-                <Snackbar
239
-                    visible={this.state.snackbarVisible}
240
-                    onDismiss={this.hideSnackBar}
241
-                    action={{
242
-                        label: 'OK',
243
-                        onPress: () => {
244
-                        },
245
-                    }}
246
-                    duration={4000}
247
-                    style={{
248
-                        bottom: CustomTabBar.TAB_BAR_HEIGHT
249
-                    }}
250
-                >
251
-                    {i18n.t("general.listUpdateFail")}
252
-                </Snackbar>
253
-            </View>
254
-        );
255
-    }
210
+  render(): React.Node {
211
+    const {props, state} = this;
212
+    let dataset = [];
213
+    if (
214
+      state.fetchedData != null ||
215
+      (state.fetchedData == null && !props.showError)
216
+    )
217
+      dataset = props.createDataset(state.fetchedData, state.refreshing);
218
+
219
+    const {containerPaddingTop} = props.collapsibleStack;
220
+    return (
221
+      <View>
222
+        <CollapsibleSectionList
223
+          sections={dataset}
224
+          extraData={props.updateData}
225
+          refreshControl={
226
+            <RefreshControl
227
+              progressViewOffset={containerPaddingTop}
228
+              refreshing={state.refreshing}
229
+              onRefresh={this.onRefresh}
230
+            />
231
+          }
232
+          renderSectionHeader={this.getRenderSectionHeader}
233
+          renderItem={this.getRenderItem}
234
+          stickySectionHeadersEnabled={props.stickyHeader}
235
+          style={{minHeight: '100%'}}
236
+          ListHeaderComponent={
237
+            props.renderListHeaderComponent != null
238
+              ? props.renderListHeaderComponent(state.fetchedData)
239
+              : null
240
+          }
241
+          ListEmptyComponent={
242
+            state.refreshing ? (
243
+              <BasicLoadingScreen />
244
+            ) : (
245
+              <ErrorView
246
+                navigation={props.navigation}
247
+                errorCode={ERROR_TYPE.CONNECTION_ERROR}
248
+                onRefresh={this.onRefresh}
249
+              />
250
+            )
251
+          }
252
+          getItemLayout={props.itemHeight != null ? this.getItemLayout : null}
253
+          onScroll={this.onScroll}
254
+          hasTab
255
+        />
256
+        <Snackbar
257
+          visible={state.snackbarVisible}
258
+          onDismiss={this.hideSnackBar}
259
+          action={{
260
+            label: 'OK',
261
+            onPress: () => {},
262
+          }}
263
+          duration={4000}
264
+          style={{
265
+            bottom: CustomTabBar.TAB_BAR_HEIGHT,
266
+          }}>
267
+          {i18n.t('general.listUpdateFail')}
268
+        </Snackbar>
269
+      </View>
270
+    );
271
+  }
256 272
 }
257 273
 
258 274
 export default withCollapsible(WebSectionList);

+ 64
- 48
src/screens/Services/Proximo/ProximoAboutScreen.js View File

@@ -2,58 +2,74 @@
2 2
 
3 3
 import * as React from 'react';
4 4
 import {Image, View} from 'react-native';
5
-import i18n from "i18n-js";
5
+import i18n from 'i18n-js';
6 6
 import {Card, List, Paragraph, Text} from 'react-native-paper';
7
-import CustomTabBar from "../../../components/Tabbar/CustomTabBar";
8
-import {StackNavigationProp} from "@react-navigation/stack";
9
-import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
7
+import CustomTabBar from '../../../components/Tabbar/CustomTabBar';
8
+import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
10 9
 
11
-type Props = {
12
-    navigation: StackNavigationProp,
13
-};
14
-
15
-const LOGO = "https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png";
10
+const LOGO = 'https://etud.insa-toulouse.fr/~amicale_app/images/Proximo.png';
16 11
 
17 12
 /**
18 13
  * Class defining the proximo about screen.
19 14
  */
20
-export default class ProximoAboutScreen extends React.Component<Props> {
21
-
22
-    render() {
23
-        return (
24
-            <CollapsibleScrollView style={{padding: 5}}>
25
-                <View style={{
26
-                    width: '100%',
27
-                    height: 100,
28
-                    marginTop: 20,
29
-                    marginBottom: 20,
30
-                    justifyContent: 'center',
31
-                    alignItems: 'center'
32
-                }}>
33
-                    <Image
34
-                        source={{uri: LOGO}}
35
-                        style={{height: '100%', width: '100%', resizeMode: "contain"}}/>
36
-                </View>
37
-                <Text>{i18n.t('screens.proximo.description')}</Text>
38
-                <Card style={{margin: 5}}>
39
-                    <Card.Title
40
-                        title={i18n.t('screens.proximo.openingHours')}
41
-                        left={props => <List.Icon {...props} icon={'clock-outline'}/>}
42
-                    />
43
-                    <Card.Content>
44
-                        <Paragraph>18h30 - 19h30</Paragraph>
45
-                    </Card.Content>
46
-                </Card>
47
-                <Card style={{margin: 5, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
48
-                    <Card.Title
49
-                        title={i18n.t('screens.proximo.paymentMethods')}
50
-                        left={props => <List.Icon {...props} icon={'cash'}/>}
51
-                    />
52
-                    <Card.Content>
53
-                        <Paragraph>{i18n.t('screens.proximo.paymentMethodsDescription')}</Paragraph>
54
-                    </Card.Content>
55
-                </Card>
56
-            </CollapsibleScrollView>
57
-        );
58
-    }
15
+// eslint-disable-next-line react/prefer-stateless-function
16
+export default class ProximoAboutScreen extends React.Component<null> {
17
+  render(): React.Node {
18
+    return (
19
+      <CollapsibleScrollView style={{padding: 5}}>
20
+        <View
21
+          style={{
22
+            width: '100%',
23
+            height: 100,
24
+            marginTop: 20,
25
+            marginBottom: 20,
26
+            justifyContent: 'center',
27
+            alignItems: 'center',
28
+          }}>
29
+          <Image
30
+            source={{uri: LOGO}}
31
+            style={{height: '100%', width: '100%', resizeMode: 'contain'}}
32
+          />
33
+        </View>
34
+        <Text>{i18n.t('screens.proximo.description')}</Text>
35
+        <Card style={{margin: 5}}>
36
+          <Card.Title
37
+            title={i18n.t('screens.proximo.openingHours')}
38
+            left={({
39
+              size,
40
+              color,
41
+            }: {
42
+              size: number,
43
+              color: string,
44
+            }): React.Node => (
45
+              <List.Icon size={size} color={color} icon="clock-outline" />
46
+            )}
47
+          />
48
+          <Card.Content>
49
+            <Paragraph>18h30 - 19h30</Paragraph>
50
+          </Card.Content>
51
+        </Card>
52
+        <Card
53
+          style={{margin: 5, marginBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
54
+          <Card.Title
55
+            title={i18n.t('screens.proximo.paymentMethods')}
56
+            left={({
57
+              size,
58
+              color,
59
+            }: {
60
+              size: number,
61
+              color: string,
62
+            }): React.Node => (
63
+              <List.Icon size={size} color={color} icon="cash" />
64
+            )}
65
+          />
66
+          <Card.Content>
67
+            <Paragraph>
68
+              {i18n.t('screens.proximo.paymentMethodsDescription')}
69
+            </Paragraph>
70
+          </Card.Content>
71
+        </Card>
72
+      </CollapsibleScrollView>
73
+    );
74
+  }
59 75
 }

+ 337
- 279
src/screens/Services/Proximo/ProximoListScreen.js View File

@@ -1,323 +1,381 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {Image, Platform, ScrollView, View} from "react-native";
5
-import i18n from "i18n-js";
6
-import CustomModal from "../../../components/Overrides/CustomModal";
7
-import {RadioButton, Searchbar, Subheading, Text, Title, withTheme} from "react-native-paper";
8
-import {stringMatchQuery} from "../../../utils/Search";
9
-import ProximoListItem from "../../../components/Lists/Proximo/ProximoListItem";
10
-import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
11
-import {StackNavigationProp} from "@react-navigation/stack";
12
-import type {CustomTheme} from "../../../managers/ThemeManager";
13
-import CollapsibleFlatList from "../../../components/Collapsible/CollapsibleFlatList";
4
+import {Image, Platform, ScrollView, View} from 'react-native';
5
+import i18n from 'i18n-js';
6
+import {
7
+  RadioButton,
8
+  Searchbar,
9
+  Subheading,
10
+  Text,
11
+  Title,
12
+  withTheme,
13
+} from 'react-native-paper';
14
+import {StackNavigationProp} from '@react-navigation/stack';
15
+import {Modalize} from 'react-native-modalize';
16
+import CustomModal from '../../../components/Overrides/CustomModal';
17
+import {stringMatchQuery} from '../../../utils/Search';
18
+import ProximoListItem from '../../../components/Lists/Proximo/ProximoListItem';
19
+import MaterialHeaderButtons, {
20
+  Item,
21
+} from '../../../components/Overrides/CustomHeaderButton';
22
+import type {CustomTheme} from '../../../managers/ThemeManager';
23
+import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
24
+import type {ProximoArticleType} from './ProximoMainScreen';
14 25
 
15
-function sortPrice(a, b) {
16
-    return a.price - b.price;
26
+function sortPrice(a: ProximoArticleType, b: ProximoArticleType): number {
27
+  return parseInt(a.price, 10) - parseInt(b.price, 10);
17 28
 }
18 29
 
19
-function sortPriceReverse(a, b) {
20
-    return b.price - a.price;
30
+function sortPriceReverse(
31
+  a: ProximoArticleType,
32
+  b: ProximoArticleType,
33
+): number {
34
+  return parseInt(b.price, 10) - parseInt(a.price, 10);
21 35
 }
22 36
 
23
-function sortName(a, b) {
24
-    if (a.name.toLowerCase() < b.name.toLowerCase())
25
-        return -1;
26
-    if (a.name.toLowerCase() > b.name.toLowerCase())
27
-        return 1;
28
-    return 0;
37
+function sortName(a: ProximoArticleType, b: ProximoArticleType): number {
38
+  if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
39
+  if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
40
+  return 0;
29 41
 }
30 42
 
31
-function sortNameReverse(a, b) {
32
-    if (a.name.toLowerCase() < b.name.toLowerCase())
33
-        return 1;
34
-    if (a.name.toLowerCase() > b.name.toLowerCase())
35
-        return -1;
36
-    return 0;
43
+function sortNameReverse(a: ProximoArticleType, b: ProximoArticleType): number {
44
+  if (a.name.toLowerCase() < b.name.toLowerCase()) return 1;
45
+  if (a.name.toLowerCase() > b.name.toLowerCase()) return -1;
46
+  return 0;
37 47
 }
38 48
 
39 49
 const LIST_ITEM_HEIGHT = 84;
40 50
 
41
-type Props = {
42
-    navigation: StackNavigationProp,
43
-    route: { params: { data: { data: Object }, shouldFocusSearchBar: boolean } },
44
-    theme: CustomTheme,
45
-}
51
+type PropsType = {
52
+  navigation: StackNavigationProp,
53
+  route: {
54
+    params: {
55
+      data: {data: Array<ProximoArticleType>},
56
+      shouldFocusSearchBar: boolean,
57
+    },
58
+  },
59
+  theme: CustomTheme,
60
+};
46 61
 
47
-type State = {
48
-    currentSortMode: number,
49
-    modalCurrentDisplayItem: React.Node,
50
-    currentSearchString: string,
62
+type StateType = {
63
+  currentSortMode: number,
64
+  modalCurrentDisplayItem: React.Node,
65
+  currentSearchString: string,
51 66
 };
52 67
 
53 68
 /**
54
- * Class defining proximo's article list of a certain category.
69
+ * Class defining Proximo article list of a certain category.
55 70
  */
56
-class ProximoListScreen extends React.Component<Props, State> {
57
-
58
-    modalRef: Object;
59
-    listData: Array<Object>;
60
-    shouldFocusSearchBar: boolean;
61
-
62
-    constructor(props) {
63
-        super(props);
64
-        this.listData = this.props.route.params['data']['data'].sort(sortName);
65
-        this.shouldFocusSearchBar = this.props.route.params['shouldFocusSearchBar'];
66
-        this.state = {
67
-            currentSearchString: '',
68
-            currentSortMode: 3,
69
-            modalCurrentDisplayItem: null,
70
-        };
71
-    }
71
+class ProximoListScreen extends React.Component<PropsType, StateType> {
72
+  modalRef: Modalize | null;
72 73
 
74
+  listData: Array<ProximoArticleType>;
73 75
 
74
-    /**
75
-     * Creates the header content
76
-     */
77
-    componentDidMount() {
78
-        this.props.navigation.setOptions({
79
-            headerRight: this.getSortMenuButton,
80
-            headerTitle: this.getSearchBar,
81
-            headerBackTitleVisible: false,
82
-            headerTitleContainerStyle: Platform.OS === 'ios' ?
83
-                {marginHorizontal: 0, width: '70%'} :
84
-                {marginHorizontal: 0, right: 50, left: 50},
85
-        });
86
-    }
76
+  shouldFocusSearchBar: boolean;
87 77
 
88
-    /**
89
-     * Gets the header search bar
90
-     *
91
-     * @return {*}
92
-     */
93
-    getSearchBar = () => {
94
-        return (
95
-            <Searchbar
96
-                placeholder={i18n.t('screens.proximo.search')}
97
-                onChangeText={this.onSearchStringChange}
98
-            />
99
-        );
78
+  constructor(props: PropsType) {
79
+    super(props);
80
+    this.listData = props.route.params.data.data.sort(sortName);
81
+    this.shouldFocusSearchBar = props.route.params.shouldFocusSearchBar;
82
+    this.state = {
83
+      currentSearchString: '',
84
+      currentSortMode: 3,
85
+      modalCurrentDisplayItem: null,
100 86
     };
87
+  }
101 88
 
102
-    /**
103
-     * Gets the sort menu header button
104
-     *
105
-     * @return {*}
106
-     */
107
-    getSortMenuButton = () => {
108
-        return <MaterialHeaderButtons>
109
-            <Item title="main" iconName="sort" onPress={this.onSortMenuPress}/>
110
-        </MaterialHeaderButtons>;
111
-    };
89
+  /**
90
+   * Creates the header content
91
+   */
92
+  componentDidMount() {
93
+    const {navigation} = this.props;
94
+    navigation.setOptions({
95
+      headerRight: this.getSortMenuButton,
96
+      headerTitle: this.getSearchBar,
97
+      headerBackTitleVisible: false,
98
+      headerTitleContainerStyle:
99
+        Platform.OS === 'ios'
100
+          ? {marginHorizontal: 0, width: '70%'}
101
+          : {marginHorizontal: 0, right: 50, left: 50},
102
+    });
103
+  }
112 104
 
113
-    /**
114
-     * Callback used when clicking on the sort menu button.
115
-     * It will open the modal to show a sort selection
116
-     */
117
-    onSortMenuPress = () => {
118
-        this.setState({
119
-            modalCurrentDisplayItem: this.getModalSortMenu()
120
-        });
121
-        if (this.modalRef) {
122
-            this.modalRef.open();
123
-        }
124
-    };
105
+  /**
106
+   * Callback used when clicking on the sort menu button.
107
+   * It will open the modal to show a sort selection
108
+   */
109
+  onSortMenuPress = () => {
110
+    this.setState({
111
+      modalCurrentDisplayItem: this.getModalSortMenu(),
112
+    });
113
+    if (this.modalRef) {
114
+      this.modalRef.open();
115
+    }
116
+  };
117
+
118
+  /**
119
+   * Callback used when the search changes
120
+   *
121
+   * @param str The new search string
122
+   */
123
+  onSearchStringChange = (str: string) => {
124
+    this.setState({currentSearchString: str});
125
+  };
125 126
 
126
-    /**
127
-     * Sets the current sort mode.
128
-     *
129
-     * @param mode The number representing the mode
130
-     */
131
-    setSortMode(mode: number) {
132
-        this.setState({
133
-            currentSortMode: mode,
134
-        });
135
-        switch (mode) {
136
-            case 1:
137
-                this.listData.sort(sortPrice);
138
-                break;
139
-            case 2:
140
-                this.listData.sort(sortPriceReverse);
141
-                break;
142
-            case 3:
143
-                this.listData.sort(sortName);
144
-                break;
145
-            case 4:
146
-                this.listData.sort(sortNameReverse);
147
-                break;
148
-        }
149
-        if (this.modalRef && mode !== this.state.currentSortMode) {
150
-            this.modalRef.close();
151
-        }
127
+  /**
128
+   * Callback used when clicking an article in the list.
129
+   * It opens the modal to show detailed information about the article
130
+   *
131
+   * @param item The article pressed
132
+   */
133
+  onListItemPress(item: ProximoArticleType) {
134
+    this.setState({
135
+      modalCurrentDisplayItem: this.getModalItemContent(item),
136
+    });
137
+    if (this.modalRef) {
138
+      this.modalRef.open();
152 139
     }
140
+  }
153 141
 
154
-    /**
155
-     * Gets a color depending on the quantity available
156
-     *
157
-     * @param availableStock The quantity available
158
-     * @return
159
-     */
160
-    getStockColor(availableStock: number) {
161
-        let color: string;
162
-        if (availableStock > 3)
163
-            color = this.props.theme.colors.success;
164
-        else if (availableStock > 0)
165
-            color = this.props.theme.colors.warning;
166
-        else
167
-            color = this.props.theme.colors.danger;
168
-        return color;
142
+  /**
143
+   * Sets the current sort mode.
144
+   *
145
+   * @param mode The number representing the mode
146
+   */
147
+  setSortMode(mode: string) {
148
+    const {currentSortMode} = this.state;
149
+    const currentMode = parseInt(mode, 10);
150
+    this.setState({
151
+      currentSortMode: currentMode,
152
+    });
153
+    switch (currentMode) {
154
+      case 1:
155
+        this.listData.sort(sortPrice);
156
+        break;
157
+      case 2:
158
+        this.listData.sort(sortPriceReverse);
159
+        break;
160
+      case 3:
161
+        this.listData.sort(sortName);
162
+        break;
163
+      case 4:
164
+        this.listData.sort(sortNameReverse);
165
+        break;
166
+      default:
167
+        this.listData.sort(sortName);
168
+        break;
169 169
     }
170
+    if (this.modalRef && currentMode !== currentSortMode) this.modalRef.close();
171
+  }
170 172
 
171
-    /**
172
-     * Callback used when the search changes
173
-     *
174
-     * @param str The new search string
175
-     */
176
-    onSearchStringChange = (str: string) => {
177
-        this.setState({currentSearchString: str})
178
-    };
173
+  /**
174
+   * Gets a color depending on the quantity available
175
+   *
176
+   * @param availableStock The quantity available
177
+   * @return
178
+   */
179
+  getStockColor(availableStock: number): string {
180
+    const {theme} = this.props;
181
+    let color: string;
182
+    if (availableStock > 3) color = theme.colors.success;
183
+    else if (availableStock > 0) color = theme.colors.warning;
184
+    else color = theme.colors.danger;
185
+    return color;
186
+  }
179 187
 
180
-    /**
181
-     * Gets the modal content depending on the given article
182
-     *
183
-     * @param item The article to display
184
-     * @return {*}
185
-     */
186
-    getModalItemContent(item: Object) {
187
-        return (
188
-            <View style={{
189
-                flex: 1,
190
-                padding: 20
191
-            }}>
192
-                <Title>{item.name}</Title>
193
-                <View style={{
194
-                    flexDirection: 'row',
195
-                    width: '100%',
196
-                    marginTop: 10,
197
-                }}>
198
-                    <Subheading style={{
199
-                        color: this.getStockColor(parseInt(item.quantity)),
200
-                    }}>
201
-                        {item.quantity + ' ' + i18n.t('screens.proximo.inStock')}
202
-                    </Subheading>
203
-                    <Subheading style={{marginLeft: 'auto'}}>{item.price}€</Subheading>
204
-                </View>
188
+  /**
189
+   * Gets the sort menu header button
190
+   *
191
+   * @return {*}
192
+   */
193
+  getSortMenuButton = (): React.Node => {
194
+    return (
195
+      <MaterialHeaderButtons>
196
+        <Item title="main" iconName="sort" onPress={this.onSortMenuPress} />
197
+      </MaterialHeaderButtons>
198
+    );
199
+  };
205 200
 
206
-                <ScrollView>
207
-                    <View style={{width: '100%', height: 150, marginTop: 20, marginBottom: 20}}>
208
-                        <Image style={{flex: 1, resizeMode: "contain"}}
209
-                               source={{uri: item.image}}/>
210
-                    </View>
211
-                    <Text>{item.description}</Text>
212
-                </ScrollView>
213
-            </View>
214
-        );
215
-    }
201
+  /**
202
+   * Gets the header search bar
203
+   *
204
+   * @return {*}
205
+   */
206
+  getSearchBar = (): React.Node => {
207
+    return (
208
+      <Searchbar
209
+        placeholder={i18n.t('screens.proximo.search')}
210
+        onChangeText={this.onSearchStringChange}
211
+      />
212
+    );
213
+  };
216 214
 
217
-    /**
218
-     * Gets the modal content to display a sort menu
219
-     *
220
-     * @return {*}
221
-     */
222
-    getModalSortMenu() {
223
-        return (
224
-            <View style={{
225
-                flex: 1,
226
-                padding: 20
215
+  /**
216
+   * Gets the modal content depending on the given article
217
+   *
218
+   * @param item The article to display
219
+   * @return {*}
220
+   */
221
+  getModalItemContent(item: ProximoArticleType): React.Node {
222
+    return (
223
+      <View
224
+        style={{
225
+          flex: 1,
226
+          padding: 20,
227
+        }}>
228
+        <Title>{item.name}</Title>
229
+        <View
230
+          style={{
231
+            flexDirection: 'row',
232
+            width: '100%',
233
+            marginTop: 10,
234
+          }}>
235
+          <Subheading
236
+            style={{
237
+              color: this.getStockColor(parseInt(item.quantity, 10)),
227 238
             }}>
228
-                <Title style={{marginBottom: 10}}>{i18n.t('screens.proximo.sortOrder')}</Title>
229
-                <RadioButton.Group
230
-                    onValueChange={value => this.setSortMode(value)}
231
-                    value={this.state.currentSortMode}
232
-                >
233
-                    <RadioButton.Item label={i18n.t('screens.proximo.sortPrice')} value={1}/>
234
-                    <RadioButton.Item label={i18n.t('screens.proximo.sortPriceReverse')} value={2}/>
235
-                    <RadioButton.Item label={i18n.t('screens.proximo.sortName')} value={3}/>
236
-                    <RadioButton.Item label={i18n.t('screens.proximo.sortNameReverse')} value={4}/>
237
-                </RadioButton.Group>
238
-            </View>
239
-        );
240
-    }
239
+            {`${item.quantity} ${i18n.t('screens.proximo.inStock')}`}
240
+          </Subheading>
241
+          <Subheading style={{marginLeft: 'auto'}}>{item.price}€</Subheading>
242
+        </View>
241 243
 
242
-    /**
243
-     * Callback used when clicking an article in the list.
244
-     * It opens the modal to show detailed information about the article
245
-     *
246
-     * @param item The article pressed
247
-     */
248
-    onListItemPress(item: Object) {
249
-        this.setState({
250
-            modalCurrentDisplayItem: this.getModalItemContent(item)
251
-        });
252
-        if (this.modalRef) {
253
-            this.modalRef.open();
254
-        }
255
-    }
244
+        <ScrollView>
245
+          <View
246
+            style={{
247
+              width: '100%',
248
+              height: 150,
249
+              marginTop: 20,
250
+              marginBottom: 20,
251
+            }}>
252
+            <Image
253
+              style={{flex: 1, resizeMode: 'contain'}}
254
+              source={{uri: item.image}}
255
+            />
256
+          </View>
257
+          <Text>{item.description}</Text>
258
+        </ScrollView>
259
+      </View>
260
+    );
261
+  }
256 262
 
257
-    /**
258
-     * Gets a render item for the given article
259
-     *
260
-     * @param item The article to render
261
-     * @return {*}
262
-     */
263
-    renderItem = ({item}: Object) => {
264
-        if (stringMatchQuery(item.name, this.state.currentSearchString)) {
265
-            const onPress = this.onListItemPress.bind(this, item);
266
-            const color = this.getStockColor(parseInt(item.quantity));
267
-            return (
268
-                <ProximoListItem
269
-                    item={item}
270
-                    onPress={onPress}
271
-                    color={color}
272
-                    height={LIST_ITEM_HEIGHT}
273
-                />
274
-            );
275
-        } else
276
-            return null;
277
-    };
263
+  /**
264
+   * Gets the modal content to display a sort menu
265
+   *
266
+   * @return {*}
267
+   */
268
+  getModalSortMenu(): React.Node {
269
+    const {currentSortMode} = this.state;
270
+    return (
271
+      <View
272
+        style={{
273
+          flex: 1,
274
+          padding: 20,
275
+        }}>
276
+        <Title style={{marginBottom: 10}}>
277
+          {i18n.t('screens.proximo.sortOrder')}
278
+        </Title>
279
+        <RadioButton.Group
280
+          onValueChange={(value: string) => {
281
+            this.setSortMode(value);
282
+          }}
283
+          value={currentSortMode}>
284
+          <RadioButton.Item
285
+            label={i18n.t('screens.proximo.sortPrice')}
286
+            value={1}
287
+          />
288
+          <RadioButton.Item
289
+            label={i18n.t('screens.proximo.sortPriceReverse')}
290
+            value={2}
291
+          />
292
+          <RadioButton.Item
293
+            label={i18n.t('screens.proximo.sortName')}
294
+            value={3}
295
+          />
296
+          <RadioButton.Item
297
+            label={i18n.t('screens.proximo.sortNameReverse')}
298
+            value={4}
299
+          />
300
+        </RadioButton.Group>
301
+      </View>
302
+    );
303
+  }
278 304
 
279
-    /**
280
-     * Extracts a key for the given article
281
-     *
282
-     * @param item The article to extract the key from
283
-     * @return {*} The extracted key
284
-     */
285
-    keyExtractor(item: Object) {
286
-        return item.name + item.code;
305
+  /**
306
+   * Gets a render item for the given article
307
+   *
308
+   * @param item The article to render
309
+   * @return {*}
310
+   */
311
+  getRenderItem = ({item}: {item: ProximoArticleType}): React.Node => {
312
+    const {currentSearchString} = this.state;
313
+    if (stringMatchQuery(item.name, currentSearchString)) {
314
+      const onPress = () => {
315
+        this.onListItemPress(item);
316
+      };
317
+      const color = this.getStockColor(parseInt(item.quantity, 10));
318
+      return (
319
+        <ProximoListItem
320
+          item={item}
321
+          onPress={onPress}
322
+          color={color}
323
+          height={LIST_ITEM_HEIGHT}
324
+        />
325
+      );
287 326
     }
327
+    return null;
328
+  };
288 329
 
289
-    /**
290
-     * Callback used when receiving the modal ref
291
-     *
292
-     * @param ref
293
-     */
294
-    onModalRef = (ref: Object) => {
295
-        this.modalRef = ref;
296
-    };
330
+  /**
331
+   * Extracts a key for the given article
332
+   *
333
+   * @param item The article to extract the key from
334
+   * @return {string} The extracted key
335
+   */
336
+  keyExtractor = (item: ProximoArticleType): string => item.name + item.code;
297 337
 
298
-    itemLayout = (data, index) => ({length: LIST_ITEM_HEIGHT, offset: LIST_ITEM_HEIGHT * index, index});
338
+  /**
339
+   * Callback used when receiving the modal ref
340
+   *
341
+   * @param ref
342
+   */
343
+  onModalRef = (ref: Modalize) => {
344
+    this.modalRef = ref;
345
+  };
299 346
 
300
-    render() {
301
-        return (
302
-            <View style={{
303
-                height: '100%'
304
-            }}>
305
-                <CustomModal onRef={this.onModalRef}>
306
-                    {this.state.modalCurrentDisplayItem}
307
-                </CustomModal>
308
-                <CollapsibleFlatList
309
-                    data={this.listData}
310
-                    extraData={this.state.currentSearchString + this.state.currentSortMode}
311
-                    keyExtractor={this.keyExtractor}
312
-                    renderItem={this.renderItem}
313
-                    // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
314
-                    removeClippedSubviews={true}
315
-                    getItemLayout={this.itemLayout}
316
-                    initialNumToRender={10}
317
-                />
318
-            </View>
319
-        );
320
-    }
347
+  itemLayout = (
348
+    data: ProximoArticleType,
349
+    index: number,
350
+  ): {length: number, offset: number, index: number} => ({
351
+    length: LIST_ITEM_HEIGHT,
352
+    offset: LIST_ITEM_HEIGHT * index,
353
+    index,
354
+  });
355
+
356
+  render(): React.Node {
357
+    const {state} = this;
358
+    return (
359
+      <View
360
+        style={{
361
+          height: '100%',
362
+        }}>
363
+        <CustomModal onRef={this.onModalRef}>
364
+          {state.modalCurrentDisplayItem}
365
+        </CustomModal>
366
+        <CollapsibleFlatList
367
+          data={this.listData}
368
+          extraData={state.currentSearchString + state.currentSortMode}
369
+          keyExtractor={this.keyExtractor}
370
+          renderItem={this.getRenderItem}
371
+          // Performance props, see https://reactnative.dev/docs/optimizing-flatlist-configuration
372
+          removeClippedSubviews
373
+          getItemLayout={this.itemLayout}
374
+          initialNumToRender={10}
375
+        />
376
+      </View>
377
+    );
378
+  }
321 379
 }
322 380
 
323 381
 export default withTheme(ProximoListScreen);

+ 260
- 204
src/screens/Services/Proximo/ProximoMainScreen.js View File

@@ -1,233 +1,289 @@
1 1
 // @flow
2 2
 
3 3
 import * as React from 'react';
4
-import {View} from 'react-native'
5
-import i18n from "i18n-js";
6
-import WebSectionList from "../../../components/Screens/WebSectionList";
4
+import i18n from 'i18n-js';
7 5
 import {List, withTheme} from 'react-native-paper';
8
-import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
9
-import {StackNavigationProp} from "@react-navigation/stack";
10
-import type {CustomTheme} from "../../../managers/ThemeManager";
6
+import {StackNavigationProp} from '@react-navigation/stack';
7
+import WebSectionList from '../../../components/Screens/WebSectionList';
8
+import MaterialHeaderButtons, {
9
+  Item,
10
+} from '../../../components/Overrides/CustomHeaderButton';
11
+import type {CustomTheme} from '../../../managers/ThemeManager';
12
+import type {SectionListDataType} from '../../../components/Screens/WebSectionList';
11 13
 
12
-const DATA_URL = "https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json";
14
+const DATA_URL = 'https://etud.insa-toulouse.fr/~proximo/data/stock-v2.json';
13 15
 const LIST_ITEM_HEIGHT = 84;
14 16
 
15
-type Props = {
16
-    navigation: StackNavigationProp,
17
-    theme: CustomTheme,
18
-}
17
+export type ProximoCategoryType = {
18
+  name: string,
19
+  icon: string,
20
+  id: string,
21
+};
19 22
 
20
-type State = {
21
-    fetchedData: Object,
22
-}
23
+export type ProximoArticleType = {
24
+  name: string,
25
+  description: string,
26
+  quantity: string,
27
+  price: string,
28
+  code: string,
29
+  id: string,
30
+  type: Array<string>,
31
+  image: string,
32
+};
33
+
34
+export type ProximoMainListItemType = {
35
+  type: ProximoCategoryType,
36
+  data: Array<ProximoArticleType>,
37
+};
38
+
39
+export type ProximoDataType = {
40
+  types: Array<ProximoCategoryType>,
41
+  articles: Array<ProximoArticleType>,
42
+};
43
+
44
+type PropsType = {
45
+  navigation: StackNavigationProp,
46
+  theme: CustomTheme,
47
+};
23 48
 
24 49
 /**
25 50
  * Class defining the main proximo screen.
26 51
  * This screen shows the different categories of articles offered by proximo.
27 52
  */
28
-class ProximoMainScreen extends React.Component<Props, State> {
29
-
30
-    articles: Object;
31
-
32
-    /**
33
-     * Function used to sort items in the list.
34
-     * Makes the All category stick to the top and sorts the others by name ascending
35
-     *
36
-     * @param a
37
-     * @param b
38
-     * @return {number}
39
-     */
40
-    static sortFinalData(a: Object, b: Object) {
41
-        let str1 = a.type.name.toLowerCase();
42
-        let str2 = b.type.name.toLowerCase();
43
-
44
-        // Make 'All' category with id -1 stick to the top
45
-        if (a.type.id === -1)
46
-            return -1;
47
-        if (b.type.id === -1)
48
-            return 1;
49
-
50
-        // Sort others by name ascending
51
-        if (str1 < str2)
52
-            return -1;
53
-        if (str1 > str2)
54
-            return 1;
55
-        return 0;
56
-    }
53
+class ProximoMainScreen extends React.Component<PropsType> {
54
+  /**
55
+   * Function used to sort items in the list.
56
+   * Makes the All category sticks to the top and sorts the others by name ascending
57
+   *
58
+   * @param a
59
+   * @param b
60
+   * @return {number}
61
+   */
62
+  static sortFinalData(
63
+    a: ProximoMainListItemType,
64
+    b: ProximoMainListItemType,
65
+  ): number {
66
+    const str1 = a.type.name.toLowerCase();
67
+    const str2 = b.type.name.toLowerCase();
57 68
 
58
-    /**
59
-     * Creates header button
60
-     */
61
-    componentDidMount() {
62
-        const rightButton = this.getHeaderButtons.bind(this);
63
-        this.props.navigation.setOptions({
64
-            headerRight: rightButton,
65
-        });
66
-    }
69
+    // Make 'All' category with id -1 stick to the top
70
+    if (a.type.id === -1) return -1;
71
+    if (b.type.id === -1) return 1;
67 72
 
68
-    /**
69
-     * Callback used when the search button is pressed.
70
-     * This will open a new ProximoListScreen with all items displayed
71
-     */
72
-    onPressSearchBtn = () => {
73
-        let searchScreenData = {
74
-            shouldFocusSearchBar: true,
75
-            data: {
76
-                type: {
77
-                    id: "0",
78
-                    name: i18n.t('screens.proximo.all'),
79
-                    icon: 'star'
80
-                },
81
-                data: this.articles !== undefined ?
82
-                    this.getAvailableArticles(this.articles, undefined) : []
83
-            },
84
-        };
85
-        this.props.navigation.navigate('proximo-list', searchScreenData);
86
-    };
73
+    // Sort others by name ascending
74
+    if (str1 < str2) return -1;
75
+    if (str1 > str2) return 1;
76
+    return 0;
77
+  }
87 78
 
88
-    /**
89
-     * Callback used when the about button is pressed.
90
-     * This will open the ProximoAboutScreen
91
-     */
92
-    onPressAboutBtn = () => {
93
-        this.props.navigation.navigate('proximo-about');
79
+  /**
80
+   * Get an array of available articles (in stock) of the given type
81
+   *
82
+   * @param articles The list of all articles
83
+   * @param type The type of articles to find (undefined for any type)
84
+   * @return {Array} The array of available articles
85
+   */
86
+  static getAvailableArticles(
87
+    articles: Array<ProximoArticleType> | null,
88
+    type: ?ProximoCategoryType,
89
+  ): Array<ProximoArticleType> {
90
+    const availableArticles = [];
91
+    if (articles != null) {
92
+      articles.forEach((article: ProximoArticleType) => {
93
+        if (
94
+          ((type != null && article.type.includes(type.id)) || type == null) &&
95
+          parseInt(article.quantity, 10) > 0
96
+        )
97
+          availableArticles.push(article);
98
+      });
94 99
     }
100
+    return availableArticles;
101
+  }
95 102
 
96
-    /**
97
-     * Gets the header buttons
98
-     * @return {*}
99
-     */
100
-    getHeaderButtons() {
101
-        return <MaterialHeaderButtons>
102
-            <Item title="magnify" iconName="magnify" onPress={this.onPressSearchBtn}/>
103
-            <Item title="information" iconName="information" onPress={this.onPressAboutBtn}/>
104
-        </MaterialHeaderButtons>;
105
-    }
103
+  articles: Array<ProximoArticleType> | null;
106 104
 
107
-    /**
108
-     * Extracts a key for the given category
109
-     *
110
-     * @param item The category to extract the key from
111
-     * @return {*} The extracted key
112
-     */
113
-    getKeyExtractor(item: Object) {
114
-        return item !== undefined ? item.type['id'] : undefined;
115
-    }
105
+  /**
106
+   * Creates header button
107
+   */
108
+  componentDidMount() {
109
+    const {navigation} = this.props;
110
+    navigation.setOptions({
111
+      headerRight: (): React.Node => this.getHeaderButtons(),
112
+    });
113
+  }
116 114
 
117
-    /**
118
-     * Creates the dataset to be used in the FlatList
119
-     *
120
-     * @param fetchedData
121
-     * @return {*}
122
-     * */
123
-    createDataset = (fetchedData: Object) => {
124
-        return [
125
-            {
126
-                title: '',
127
-                data: this.generateData(fetchedData),
128
-                keyExtractor: this.getKeyExtractor
129
-            }
130
-        ];
131
-    }
115
+  /**
116
+   * Callback used when the search button is pressed.
117
+   * This will open a new ProximoListScreen with all items displayed
118
+   */
119
+  onPressSearchBtn = () => {
120
+    const {navigation} = this.props;
121
+    const searchScreenData = {
122
+      shouldFocusSearchBar: true,
123
+      data: {
124
+        type: {
125
+          id: '0',
126
+          name: i18n.t('screens.proximo.all'),
127
+          icon: 'star',
128
+        },
129
+        data:
130
+          this.articles != null
131
+            ? ProximoMainScreen.getAvailableArticles(this.articles)
132
+            : [],
133
+      },
134
+    };
135
+    navigation.navigate('proximo-list', searchScreenData);
136
+  };
132 137
 
133
-    /**
134
-     * Generate the data using types and FetchedData.
135
-     * This will group items under the same type.
136
-     *
137
-     * @param fetchedData The array of articles represented by objects
138
-     * @returns {Array} The formatted dataset
139
-     */
140
-    generateData(fetchedData: Object) {
141
-        let finalData = [];
142
-        this.articles = undefined;
143
-        if (fetchedData.types !== undefined && fetchedData.articles !== undefined) {
144
-            let types = fetchedData.types;
145
-            this.articles = fetchedData.articles;
146
-            finalData.push({
147
-                type: {
148
-                    id: -1,
149
-                    name: i18n.t('screens.proximo.all'),
150
-                    icon: 'star'
151
-                },
152
-                data: this.getAvailableArticles(this.articles, undefined)
153
-            });
154
-            for (let i = 0; i < types.length; i++) {
155
-                finalData.push({
156
-                    type: types[i],
157
-                    data: this.getAvailableArticles(this.articles, types[i])
158
-                });
159
-
160
-            }
161
-        }
162
-        finalData.sort(ProximoMainScreen.sortFinalData);
163
-        return finalData;
164
-    }
138
+  /**
139
+   * Callback used when the about button is pressed.
140
+   * This will open the ProximoAboutScreen
141
+   */
142
+  onPressAboutBtn = () => {
143
+    const {navigation} = this.props;
144
+    navigation.navigate('proximo-about');
145
+  };
165 146
 
166
-    /**
167
-     * Get an array of available articles (in stock) of the given type
168
-     *
169
-     * @param articles The list of all articles
170
-     * @param type The type of articles to find (undefined for any type)
171
-     * @return {Array} The array of available articles
172
-     */
173
-    getAvailableArticles(articles: Array<Object>, type: ?Object) {
174
-        let availableArticles = [];
175
-        for (let k = 0; k < articles.length; k++) {
176
-            if ((type !== undefined && type !== null && articles[k]['type'].includes(type['id'])
177
-                || type === undefined)
178
-                && parseInt(articles[k]['quantity']) > 0) {
179
-                availableArticles.push(articles[k]);
180
-            }
181
-        }
182
-        return availableArticles;
183
-    }
147
+  /**
148
+   * Gets the header buttons
149
+   * @return {*}
150
+   */
151
+  getHeaderButtons(): React.Node {
152
+    return (
153
+      <MaterialHeaderButtons>
154
+        <Item
155
+          title="magnify"
156
+          iconName="magnify"
157
+          onPress={this.onPressSearchBtn}
158
+        />
159
+        <Item
160
+          title="information"
161
+          iconName="information"
162
+          onPress={this.onPressAboutBtn}
163
+        />
164
+      </MaterialHeaderButtons>
165
+    );
166
+  }
167
+
168
+  /**
169
+   * Extracts a key for the given category
170
+   *
171
+   * @param item The category to extract the key from
172
+   * @return {*} The extracted key
173
+   */
174
+  getKeyExtractor = (item: ProximoMainListItemType): string => item.type.id;
184 175
 
185
-    /**
186
-     * Gets the given category render item
187
-     *
188
-     * @param item The category to render
189
-     * @return {*}
190
-     */
191
-    getRenderItem = ({item}: Object) => {
192
-        let dataToSend = {
193
-            shouldFocusSearchBar: false,
194
-            data: item,
195
-        };
196
-        const subtitle = item.data.length + " " + (item.data.length > 1 ? i18n.t('screens.proximo.articles') : i18n.t('screens.proximo.article'));
197
-        const onPress = this.props.navigation.navigate.bind(this, 'proximo-list', dataToSend);
198
-        if (item.data.length > 0) {
199
-            return (
200
-                <List.Item
201
-                    title={item.type.name}
202
-                    description={subtitle}
203
-                    onPress={onPress}
204
-                    left={props => <List.Icon
205
-                        {...props}
206
-                        icon={item.type.icon}
207
-                        color={this.props.theme.colors.primary}/>}
208
-                    right={props => <List.Icon {...props} icon={'chevron-right'}/>}
209
-                    style={{
210
-                        height: LIST_ITEM_HEIGHT,
211
-                        justifyContent: 'center',
212
-                    }}
213
-                />
214
-            );
215
-        } else
216
-            return <View/>;
176
+  /**
177
+   * Gets the given category render item
178
+   *
179
+   * @param item The category to render
180
+   * @return {*}
181
+   */
182
+  getRenderItem = ({item}: {item: ProximoMainListItemType}): React.Node => {
183
+    const {navigation, theme} = this.props;
184
+    const dataToSend = {
185
+      shouldFocusSearchBar: false,
186
+      data: item,
187
+    };
188
+    const subtitle = `${item.data.length} ${
189
+      item.data.length > 1
190
+        ? i18n.t('screens.proximo.articles')
191
+        : i18n.t('screens.proximo.article')
192
+    }`;
193
+    const onPress = () => {
194
+      navigation.navigate('proximo-list', dataToSend);
195
+    };
196
+    if (item.data.length > 0) {
197
+      return (
198
+        <List.Item
199
+          title={item.type.name}
200
+          description={subtitle}
201
+          onPress={onPress}
202
+          left={({size}: {size: number}): React.Node => (
203
+            <List.Icon
204
+              size={size}
205
+              icon={item.type.icon}
206
+              color={theme.colors.primary}
207
+            />
208
+          )}
209
+          right={({size, color}: {size: number, color: string}): React.Node => (
210
+            <List.Icon size={size} color={color} icon="chevron-right" />
211
+          )}
212
+          style={{
213
+            height: LIST_ITEM_HEIGHT,
214
+            justifyContent: 'center',
215
+          }}
216
+        />
217
+      );
217 218
     }
219
+    return null;
220
+  };
221
+
222
+  /**
223
+   * Creates the dataset to be used in the FlatList
224
+   *
225
+   * @param fetchedData
226
+   * @return {*}
227
+   * */
228
+  createDataset = (
229
+    fetchedData: ProximoDataType | null,
230
+  ): SectionListDataType<ProximoMainListItemType> => {
231
+    return [
232
+      {
233
+        title: '',
234
+        data: this.generateData(fetchedData),
235
+        keyExtractor: this.getKeyExtractor,
236
+      },
237
+    ];
238
+  };
218 239
 
219
-    render() {
220
-        const nav = this.props.navigation;
221
-        return (
222
-            <WebSectionList
223
-                createDataset={this.createDataset}
224
-                navigation={nav}
225
-                autoRefreshTime={0}
226
-                refreshOnFocus={false}
227
-                fetchUrl={DATA_URL}
228
-                renderItem={this.getRenderItem}/>
229
-        );
240
+  /**
241
+   * Generate the data using types and FetchedData.
242
+   * This will group items under the same type.
243
+   *
244
+   * @param fetchedData The array of articles represented by objects
245
+   * @returns {Array} The formatted dataset
246
+   */
247
+  generateData(
248
+    fetchedData: ProximoDataType | null,
249
+  ): Array<ProximoMainListItemType> {
250
+    const finalData: Array<ProximoMainListItemType> = [];
251
+    this.articles = null;
252
+    if (fetchedData != null) {
253
+      const {types} = fetchedData;
254
+      this.articles = fetchedData.articles;
255
+      finalData.push({
256
+        type: {
257
+          id: '-1',
258
+          name: i18n.t('screens.proximo.all'),
259
+          icon: 'star',
260
+        },
261
+        data: ProximoMainScreen.getAvailableArticles(this.articles),
262
+      });
263
+      types.forEach((type: ProximoCategoryType) => {
264
+        finalData.push({
265
+          type,
266
+          data: ProximoMainScreen.getAvailableArticles(this.articles, type),
267
+        });
268
+      });
230 269
     }
270
+    finalData.sort(ProximoMainScreen.sortFinalData);
271
+    return finalData;
272
+  }
273
+
274
+  render(): React.Node {
275
+    const {navigation} = this.props;
276
+    return (
277
+      <WebSectionList
278
+        createDataset={this.createDataset}
279
+        navigation={navigation}
280
+        autoRefreshTime={0}
281
+        refreshOnFocus={false}
282
+        fetchUrl={DATA_URL}
283
+        renderItem={this.getRenderItem}
284
+      />
285
+    );
286
+  }
231 287
 }
232 288
 
233 289
 export default withTheme(ProximoMainScreen);

Loading…
Cancel
Save