Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProximoMainScreen.tsx 8.2KB

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