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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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] = useState<
  116. React.ReactChild | undefined
  117. >();
  118. const sortModes = [sortPrice, sortPriceReverse, sortName, sortNameReverse];
  119. useLayoutEffect(() => {
  120. navigation.setOptions({
  121. headerRight: getSortMenuButton,
  122. headerTitle: getSearchBar,
  123. headerBackTitleVisible: false,
  124. headerTitleContainerStyle:
  125. Platform.OS === 'ios'
  126. ? { marginHorizontal: 0, width: '70%' }
  127. : { marginHorizontal: 0, right: 50, left: 50 },
  128. });
  129. // eslint-disable-next-line react-hooks/exhaustive-deps
  130. }, [navigation, currentSortMode]);
  131. /**
  132. * Callback used when clicking on the sort menu button.
  133. * It will open the modal to show a sort selection
  134. */
  135. const onSortMenuPress = () => {
  136. setModalCurrentDisplayItem(getModalSortMenu());
  137. if (modalRef.current) {
  138. modalRef.current.open();
  139. }
  140. };
  141. /**
  142. * Callback used when clicking an article in the list.
  143. * It opens the modal to show detailed information about the article
  144. *
  145. * @param item The article pressed
  146. */
  147. const onListItemPress = (item: ProximoArticleType) => {
  148. setModalCurrentDisplayItem(getModalItemContent(item));
  149. if (modalRef.current) {
  150. modalRef.current.open();
  151. }
  152. };
  153. /**
  154. * Sets the current sort mode.
  155. *
  156. * @param mode The number representing the mode
  157. */
  158. const setSortMode = (mode: string) => {
  159. const currentMode = parseInt(mode, 10);
  160. setCurrentSortMode(currentMode);
  161. if (modalRef.current && currentMode !== currentSortMode) {
  162. modalRef.current.close();
  163. }
  164. };
  165. /**
  166. * Gets a color depending on the quantity available
  167. *
  168. * @param availableStock The quantity available
  169. * @return
  170. */
  171. const getStockColor = (availableStock: number): string => {
  172. let color: string;
  173. if (availableStock > 3) {
  174. color = theme.colors.success;
  175. } else if (availableStock > 0) {
  176. color = theme.colors.warning;
  177. } else {
  178. color = theme.colors.danger;
  179. }
  180. return color;
  181. };
  182. /**
  183. * Gets the sort menu header button
  184. *
  185. * @return {*}
  186. */
  187. const getSortMenuButton = () => {
  188. return (
  189. <MaterialHeaderButtons>
  190. <Item title="main" iconName="sort" onPress={onSortMenuPress} />
  191. </MaterialHeaderButtons>
  192. );
  193. };
  194. /**
  195. * Gets the header search bar
  196. *
  197. * @return {*}
  198. */
  199. const getSearchBar = () => {
  200. return (
  201. // @ts-ignore
  202. <Searchbar
  203. placeholder={i18n.t('screens.proximo.search')}
  204. onChangeText={setCurrentSearchString}
  205. autoFocus={props.route.params.shouldFocusSearchBar}
  206. />
  207. );
  208. };
  209. /**
  210. * Gets the modal content depending on the given article
  211. *
  212. * @param item The article to display
  213. * @return {*}
  214. */
  215. const getModalItemContent = (item: ProximoArticleType) => {
  216. return (
  217. <View style={styles.modalContainer}>
  218. <Title>{item.name}</Title>
  219. <View style={styles.modalTitleContainer}>
  220. <Subheading
  221. style={{
  222. color: getStockColor(item.quantity),
  223. }}
  224. >
  225. {`${item.quantity} ${i18n.t('screens.proximo.inStock')}`}
  226. </Subheading>
  227. <Subheading style={styles.modalTitle}>
  228. {item.price.toFixed(2)}€
  229. </Subheading>
  230. </View>
  231. <ScrollView>
  232. <View style={styles.modalContent}>
  233. <Image
  234. style={styles.image}
  235. source={{ uri: Urls.proximo.images + item.image }}
  236. />
  237. </View>
  238. <Text>{item.description}</Text>
  239. </ScrollView>
  240. </View>
  241. );
  242. };
  243. /**
  244. * Gets the modal content to display a sort menu
  245. *
  246. * @return {*}
  247. */
  248. const getModalSortMenu = () => {
  249. return (
  250. <View style={styles.modalContainer}>
  251. <Title style={styles.sortTitle}>
  252. {i18n.t('screens.proximo.sortOrder')}
  253. </Title>
  254. <RadioButton.Group
  255. onValueChange={setSortMode}
  256. value={currentSortMode.toString()}
  257. >
  258. <RadioButton.Item
  259. label={i18n.t('screens.proximo.sortPrice')}
  260. value={'0'}
  261. />
  262. <RadioButton.Item
  263. label={i18n.t('screens.proximo.sortPriceReverse')}
  264. value={'1'}
  265. />
  266. <RadioButton.Item
  267. label={i18n.t('screens.proximo.sortName')}
  268. value={'2'}
  269. />
  270. <RadioButton.Item
  271. label={i18n.t('screens.proximo.sortNameReverse')}
  272. value={'3'}
  273. />
  274. </RadioButton.Group>
  275. </View>
  276. );
  277. };
  278. /**
  279. * Gets a render item for the given article
  280. *
  281. * @param item The article to render
  282. * @return {*}
  283. */
  284. const getRenderItem = ({ item }: { item: ProximoArticleType }) => {
  285. if (stringMatchQuery(item.name, currentSearchString)) {
  286. const onPress = () => {
  287. onListItemPress(item);
  288. };
  289. const color = getStockColor(item.quantity);
  290. return (
  291. <ProximoListItem
  292. item={item}
  293. onPress={onPress}
  294. color={color}
  295. height={LIST_ITEM_HEIGHT}
  296. />
  297. );
  298. }
  299. return null;
  300. };
  301. /**
  302. * Extracts a key for the given article
  303. *
  304. * @param item The article to extract the key from
  305. * @return {string} The extracted key
  306. */
  307. const keyExtractor = (item: ProximoArticleType): string =>
  308. item.name + item.code;
  309. const createDataset = (
  310. data: ArticlesType | undefined
  311. ): SectionListDataType<ProximoArticleType> => {
  312. if (data) {
  313. return [
  314. {
  315. title: '',
  316. data: data
  317. .filter(
  318. (d) =>
  319. props.route.params.category === -1 ||
  320. props.route.params.category === d.category_id
  321. )
  322. .sort(sortModes[currentSortMode]),
  323. keyExtractor: keyExtractor,
  324. },
  325. ];
  326. } else {
  327. return [];
  328. }
  329. };
  330. return (
  331. <View style={GENERAL_STYLES.flex}>
  332. <CustomModal ref={modalRef}>{modalCurrentDisplayItem}</CustomModal>
  333. <WebSectionList
  334. request={() => readData<ArticlesType>(Urls.proximo.articles)}
  335. createDataset={createDataset}
  336. refreshOnFocus={true}
  337. renderItem={getRenderItem}
  338. extraData={currentSearchString + currentSortMode}
  339. itemHeight={LIST_ITEM_HEIGHT}
  340. cache={articles}
  341. onCacheUpdate={setArticles}
  342. />
  343. </View>
  344. );
  345. }
  346. export default ProximoListScreen;