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.

ProximoListScreen.js 11KB

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