Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
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.

ProximoListScreen.tsx 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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. import React, { useLayoutEffect, useRef, useState } from 'react';
  20. import { Image, Platform, ScrollView, StyleSheet, View } from 'react-native';
  21. import i18n from 'i18n-js';
  22. import {
  23. RadioButton,
  24. Searchbar,
  25. Subheading,
  26. Text,
  27. Title,
  28. useTheme,
  29. } from 'react-native-paper';
  30. import { Modalize } from 'react-native-modalize';
  31. import CustomModal from '../../../components/Overrides/CustomModal';
  32. import { stringMatchQuery } from '../../../utils/Search';
  33. import ProximoListItem from '../../../components/Lists/Proximo/ProximoListItem';
  34. import MaterialHeaderButtons, {
  35. Item,
  36. } from '../../../components/Overrides/CustomHeaderButton';
  37. import type { ProximoArticleType } from './ProximoMainScreen';
  38. import GENERAL_STYLES from '../../../constants/Styles';
  39. import { useNavigation } from '@react-navigation/core';
  40. import Urls from '../../../constants/Urls';
  41. import WebSectionList, {
  42. SectionListDataType,
  43. } from '../../../components/Screens/WebSectionList';
  44. import { readData } from '../../../utils/WebData';
  45. import { StackScreenProps } from '@react-navigation/stack';
  46. import {
  47. MainRoutes,
  48. MainStackParamsList,
  49. } from '../../../navigation/MainNavigator';
  50. import { useCachedProximoArticles } from '../../../context/cacheContext';
  51. function sortPrice(a: ProximoArticleType, b: ProximoArticleType): number {
  52. return a.price - b.price;
  53. }
  54. function sortPriceReverse(
  55. a: ProximoArticleType,
  56. b: ProximoArticleType
  57. ): number {
  58. return b.price - a.price;
  59. }
  60. function sortName(a: ProximoArticleType, b: ProximoArticleType): number {
  61. if (a.name.toLowerCase() < b.name.toLowerCase()) {
  62. return -1;
  63. }
  64. if (a.name.toLowerCase() > b.name.toLowerCase()) {
  65. return 1;
  66. }
  67. return 0;
  68. }
  69. function sortNameReverse(a: ProximoArticleType, b: ProximoArticleType): number {
  70. if (a.name.toLowerCase() < b.name.toLowerCase()) {
  71. return 1;
  72. }
  73. if (a.name.toLowerCase() > b.name.toLowerCase()) {
  74. return -1;
  75. }
  76. return 0;
  77. }
  78. const LIST_ITEM_HEIGHT = 84;
  79. const styles = StyleSheet.create({
  80. modalContainer: {
  81. flex: 1,
  82. padding: 20,
  83. },
  84. modalTitleContainer: {
  85. flexDirection: 'row',
  86. width: '100%',
  87. marginTop: 10,
  88. },
  89. modalTitle: {
  90. marginLeft: 'auto',
  91. },
  92. modalContent: {
  93. width: '100%',
  94. height: 150,
  95. marginTop: 20,
  96. marginBottom: 20,
  97. },
  98. image: {
  99. flex: 1,
  100. resizeMode: 'contain',
  101. },
  102. sortTitle: {
  103. marginBottom: 10,
  104. },
  105. });
  106. export type ArticlesType = Array<ProximoArticleType>;
  107. type Props = StackScreenProps<MainStackParamsList, MainRoutes.ProximoList>;
  108. function ProximoListScreen(props: Props) {
  109. const navigation = useNavigation();
  110. const theme = useTheme();
  111. const { articles, setArticles } = useCachedProximoArticles();
  112. const modalRef = useRef<Modalize>(null);
  113. const [currentSearchString, setCurrentSearchString] = useState('');
  114. const [currentSortMode, setCurrentSortMode] = useState(2);
  115. const [modalCurrentDisplayItem, setModalCurrentDisplayItem] =
  116. useState<React.ReactChild | undefined>();
  117. const sortModes = [sortPrice, sortPriceReverse, sortName, sortNameReverse];
  118. useLayoutEffect(() => {
  119. navigation.setOptions({
  120. headerRight: getSortMenuButton,
  121. headerTitle: getSearchBar,
  122. headerBackTitleVisible: false,
  123. headerTitleContainerStyle:
  124. Platform.OS === 'ios'
  125. ? { marginHorizontal: 0, width: '70%' }
  126. : { marginHorizontal: 0, right: 50, left: 50 },
  127. });
  128. // eslint-disable-next-line react-hooks/exhaustive-deps
  129. }, [navigation, currentSortMode]);
  130. /**
  131. * Callback used when clicking on the sort menu button.
  132. * It will open the modal to show a sort selection
  133. */
  134. const onSortMenuPress = () => {
  135. setModalCurrentDisplayItem(getModalSortMenu());
  136. if (modalRef.current) {
  137. modalRef.current.open();
  138. }
  139. };
  140. /**
  141. * Callback used when clicking an article in the list.
  142. * It opens the modal to show detailed information about the article
  143. *
  144. * @param item The article pressed
  145. */
  146. const onListItemPress = (item: ProximoArticleType) => {
  147. setModalCurrentDisplayItem(getModalItemContent(item));
  148. if (modalRef.current) {
  149. modalRef.current.open();
  150. }
  151. };
  152. /**
  153. * Sets the current sort mode.
  154. *
  155. * @param mode The number representing the mode
  156. */
  157. const setSortMode = (mode: string) => {
  158. const currentMode = parseInt(mode, 10);
  159. setCurrentSortMode(currentMode);
  160. if (modalRef.current && currentMode !== currentSortMode) {
  161. modalRef.current.close();
  162. }
  163. };
  164. /**
  165. * Gets a color depending on the quantity available
  166. *
  167. * @param availableStock The quantity available
  168. * @return
  169. */
  170. const getStockColor = (availableStock: number): string => {
  171. let color: string;
  172. if (availableStock > 3) {
  173. color = theme.colors.success;
  174. } else if (availableStock > 0) {
  175. color = theme.colors.warning;
  176. } else {
  177. color = theme.colors.danger;
  178. }
  179. return color;
  180. };
  181. /**
  182. * Gets the sort menu header button
  183. *
  184. * @return {*}
  185. */
  186. const getSortMenuButton = () => {
  187. return (
  188. <MaterialHeaderButtons>
  189. <Item title="main" iconName="sort" onPress={onSortMenuPress} />
  190. </MaterialHeaderButtons>
  191. );
  192. };
  193. /**
  194. * Gets the header search bar
  195. *
  196. * @return {*}
  197. */
  198. const getSearchBar = () => {
  199. return (
  200. // @ts-ignore
  201. <Searchbar
  202. placeholder={i18n.t('screens.proximo.search')}
  203. onChangeText={setCurrentSearchString}
  204. autoFocus={props.route.params.shouldFocusSearchBar}
  205. />
  206. );
  207. };
  208. /**
  209. * Gets the modal content depending on the given article
  210. *
  211. * @param item The article to display
  212. * @return {*}
  213. */
  214. const getModalItemContent = (item: ProximoArticleType) => {
  215. return (
  216. <View style={styles.modalContainer}>
  217. <Title>{item.name}</Title>
  218. <View style={styles.modalTitleContainer}>
  219. <Subheading
  220. style={{
  221. color: getStockColor(item.quantity),
  222. }}
  223. >
  224. {`${item.quantity} ${i18n.t('screens.proximo.inStock')}`}
  225. </Subheading>
  226. <Subheading style={styles.modalTitle}>
  227. {item.price.toFixed(2)}€
  228. </Subheading>
  229. </View>
  230. <ScrollView>
  231. <View style={styles.modalContent}>
  232. <Image
  233. style={styles.image}
  234. source={{ uri: Urls.proximo.images + item.image }}
  235. />
  236. </View>
  237. <Text>{item.description}</Text>
  238. </ScrollView>
  239. </View>
  240. );
  241. };
  242. /**
  243. * Gets the modal content to display a sort menu
  244. *
  245. * @return {*}
  246. */
  247. const getModalSortMenu = () => {
  248. return (
  249. <View style={styles.modalContainer}>
  250. <Title style={styles.sortTitle}>
  251. {i18n.t('screens.proximo.sortOrder')}
  252. </Title>
  253. <RadioButton.Group
  254. onValueChange={setSortMode}
  255. value={currentSortMode.toString()}
  256. >
  257. <RadioButton.Item
  258. label={i18n.t('screens.proximo.sortPrice')}
  259. value={'0'}
  260. />
  261. <RadioButton.Item
  262. label={i18n.t('screens.proximo.sortPriceReverse')}
  263. value={'1'}
  264. />
  265. <RadioButton.Item
  266. label={i18n.t('screens.proximo.sortName')}
  267. value={'2'}
  268. />
  269. <RadioButton.Item
  270. label={i18n.t('screens.proximo.sortNameReverse')}
  271. value={'3'}
  272. />
  273. </RadioButton.Group>
  274. </View>
  275. );
  276. };
  277. /**
  278. * Gets a render item for the given article
  279. *
  280. * @param item The article to render
  281. * @return {*}
  282. */
  283. const getRenderItem = ({ item }: { item: ProximoArticleType }) => {
  284. if (stringMatchQuery(item.name, currentSearchString)) {
  285. const onPress = () => {
  286. onListItemPress(item);
  287. };
  288. const color = getStockColor(item.quantity);
  289. return (
  290. <ProximoListItem
  291. item={item}
  292. onPress={onPress}
  293. color={color}
  294. height={LIST_ITEM_HEIGHT}
  295. />
  296. );
  297. }
  298. return null;
  299. };
  300. /**
  301. * Extracts a key for the given article
  302. *
  303. * @param item The article to extract the key from
  304. * @return {string} The extracted key
  305. */
  306. const keyExtractor = (item: ProximoArticleType): string =>
  307. item.name + item.code;
  308. const createDataset = (
  309. data: ArticlesType | undefined
  310. ): SectionListDataType<ProximoArticleType> => {
  311. if (data) {
  312. return [
  313. {
  314. title: '',
  315. data: data
  316. .filter(
  317. (d) =>
  318. props.route.params.category === -1 ||
  319. props.route.params.category === d.category_id
  320. )
  321. .sort(sortModes[currentSortMode]),
  322. keyExtractor: keyExtractor,
  323. },
  324. ];
  325. } else {
  326. return [];
  327. }
  328. };
  329. return (
  330. <View style={GENERAL_STYLES.flex}>
  331. <CustomModal ref={modalRef}>{modalCurrentDisplayItem}</CustomModal>
  332. <WebSectionList
  333. request={() => readData<ArticlesType>(Urls.proximo.articles)}
  334. createDataset={createDataset}
  335. refreshOnFocus={true}
  336. renderItem={getRenderItem}
  337. extraData={currentSearchString + currentSortMode}
  338. itemHeight={LIST_ITEM_HEIGHT}
  339. cache={articles}
  340. onCacheUpdate={setArticles}
  341. />
  342. </View>
  343. );
  344. }
  345. export default ProximoListScreen;