From 67cb96dd03460dacd176588761767b82228d141c Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 22 Sep 2020 19:36:48 +0200 Subject: [PATCH] Update equipment screens to use TypeScript --- .../Lists/Equipment/EquipmentListItem.tsx | 4 +- .../Equipment/EquipmentConfirmScreen.js | 121 ------------------ .../Equipment/EquipmentConfirmScreen.tsx | 104 +++++++++++++++ ...tListScreen.js => EquipmentListScreen.tsx} | 67 ++++++---- ...tRentScreen.js => EquipmentRentScreen.tsx} | 89 +++++++------ src/utils/EquipmentBooking.ts | 7 +- 6 files changed, 196 insertions(+), 196 deletions(-) delete mode 100644 src/screens/Amicale/Equipment/EquipmentConfirmScreen.js create mode 100644 src/screens/Amicale/Equipment/EquipmentConfirmScreen.tsx rename src/screens/Amicale/Equipment/{EquipmentListScreen.js => EquipmentListScreen.tsx} (81%) rename src/screens/Amicale/Equipment/{EquipmentRentScreen.js => EquipmentRentScreen.tsx} (88%) diff --git a/src/components/Lists/Equipment/EquipmentListItem.tsx b/src/components/Lists/Equipment/EquipmentListItem.tsx index 19af142..6e3e9b5 100644 --- a/src/components/Lists/Equipment/EquipmentListItem.tsx +++ b/src/components/Lists/Equipment/EquipmentListItem.tsx @@ -30,7 +30,7 @@ import { type PropsType = { navigation: StackNavigationProp; - userDeviceRentDates: [string, string]; + userDeviceRentDates: [string, string] | null; item: DeviceType; height: number; }; @@ -57,7 +57,7 @@ function EquipmentListItem(props: PropsType) { } let description; - if (isRented) { + if (isRented && userDeviceRentDates) { const start = new Date(userDeviceRentDates[0]); const end = new Date(userDeviceRentDates[1]); if (start.getTime() !== end.getTime()) { diff --git a/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js b/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js deleted file mode 100644 index b7d33fb..0000000 --- a/src/screens/Amicale/Equipment/EquipmentConfirmScreen.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019 - 2020 Arnaud Vergnet. - * - * This file is part of Campus INSAT. - * - * Campus INSAT is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Campus INSAT is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Campus INSAT. If not, see . - */ - -// @flow - -import * as React from 'react'; -import { - Button, - Caption, - Card, - Headline, - Paragraph, - withTheme, -} from 'react-native-paper'; -import {View} from 'react-native'; -import i18n from 'i18n-js'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; -import type {DeviceType} from './EquipmentListScreen'; -import {getRelativeDateString} from '../../../utils/EquipmentBooking'; -import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; - -type PropsType = { - route: { - params?: { - item?: DeviceType, - dates: [string, string], - }, - }, - theme: CustomThemeType, -}; - -class EquipmentConfirmScreen extends React.Component { - item: DeviceType | null; - - dates: [string, string] | null; - - constructor(props: PropsType) { - super(props); - if (props.route.params != null) { - if (props.route.params.item != null) this.item = props.route.params.item; - else this.item = null; - if (props.route.params.dates != null) - this.dates = props.route.params.dates; - else this.dates = null; - } - } - - render(): React.Node { - const {item, dates, props} = this; - if (item != null && dates != null) { - const start = new Date(dates[0]); - const end = new Date(dates[1]); - let buttonText; - if (start == null) buttonText = i18n.t('screens.equipment.booking'); - else if (end != null && start.getTime() !== end.getTime()) - buttonText = i18n.t('screens.equipment.bookingPeriod', { - begin: getRelativeDateString(start), - end: getRelativeDateString(end), - }); - else - buttonText = i18n.t('screens.equipment.bookingDay', { - date: getRelativeDateString(start), - }); - return ( - - - - - - {item.name} - - ({i18n.t('screens.equipment.bail', {cost: item.caution})}) - - - - - - {i18n.t('screens.equipment.bookingConfirmedMessage')} - - - - - ); - } - return null; - } -} - -export default withTheme(EquipmentConfirmScreen); diff --git a/src/screens/Amicale/Equipment/EquipmentConfirmScreen.tsx b/src/screens/Amicale/Equipment/EquipmentConfirmScreen.tsx new file mode 100644 index 0000000..33c4343 --- /dev/null +++ b/src/screens/Amicale/Equipment/EquipmentConfirmScreen.tsx @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 - 2020 Arnaud Vergnet. + * + * This file is part of Campus INSAT. + * + * Campus INSAT is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Campus INSAT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Campus INSAT. If not, see . + */ + +import * as React from 'react'; +import { + Button, + Caption, + Card, + Headline, + Paragraph, + useTheme, +} from 'react-native-paper'; +import {View} from 'react-native'; +import i18n from 'i18n-js'; +import {getRelativeDateString} from '../../../utils/EquipmentBooking'; +import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; +import {StackScreenProps} from '@react-navigation/stack'; +import {MainStackParamsList} from '../../../navigation/MainNavigator'; + +type EquipmentConfirmScreenNavigationProp = StackScreenProps< + MainStackParamsList, + 'equipment-confirm' +>; + +type Props = EquipmentConfirmScreenNavigationProp; + +function EquipmentConfirmScreen(props: Props) { + const theme = useTheme(); + const item = props.route.params?.item; + const dates = props.route.params?.dates; + + if (item != null && dates != null) { + const start = new Date(dates[0]); + const end = new Date(dates[1]); + let buttonText; + if (start == null) { + buttonText = i18n.t('screens.equipment.booking'); + } else if (end != null && start.getTime() !== end.getTime()) { + buttonText = i18n.t('screens.equipment.bookingPeriod', { + begin: getRelativeDateString(start), + end: getRelativeDateString(end), + }); + } else { + buttonText = i18n.t('screens.equipment.bookingDay', { + date: getRelativeDateString(start), + }); + } + return ( + + + + + + {item.name} + + ({i18n.t('screens.equipment.bail', {cost: item.caution})}) + + + + + + {i18n.t('screens.equipment.bookingConfirmedMessage')} + + + + + ); + } + return null; +} + +export default EquipmentConfirmScreen; diff --git a/src/screens/Amicale/Equipment/EquipmentListScreen.js b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx similarity index 81% rename from src/screens/Amicale/Equipment/EquipmentListScreen.js rename to src/screens/Amicale/Equipment/EquipmentListScreen.tsx index 21c4e0a..608f082 100644 --- a/src/screens/Amicale/Equipment/EquipmentListScreen.js +++ b/src/screens/Amicale/Equipment/EquipmentListScreen.tsx @@ -17,42 +17,38 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {View} from 'react-native'; -import {Button, withTheme} from 'react-native-paper'; +import {Button} from 'react-native-paper'; import {StackNavigationProp} from '@react-navigation/stack'; import i18n from 'i18n-js'; import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen'; -import type {ClubType} from '../Clubs/ClubListScreen'; import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem'; import MascotPopup from '../../../components/Mascot/MascotPopup'; import {MASCOT_STYLE} from '../../../components/Mascot/Mascot'; import AsyncStorageManager from '../../../managers/AsyncStorageManager'; import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList'; -import type {ApiGenericDataType} from '../../../utils/WebData'; type PropsType = { - navigation: StackNavigationProp, + navigation: StackNavigationProp; }; type StateType = { - mascotDialogVisible: boolean, + mascotDialogVisible: boolean; }; export type DeviceType = { - id: number, - name: string, - caution: number, - booked_at: Array<{begin: string, end: string}>, + id: number; + name: string; + caution: number; + booked_at: Array<{begin: string; end: string}>; }; export type RentedDeviceType = { - device_id: number, - device_name: string, - begin: string, - end: string, + device_id: number; + device_name: string; + begin: string; + end: string; }; const LIST_ITEM_HEIGHT = 64; @@ -60,12 +56,13 @@ const LIST_ITEM_HEIGHT = 64; class EquipmentListScreen extends React.Component { userRents: null | Array; - authRef: {current: null | AuthenticatedScreen}; + authRef: {current: null | AuthenticatedScreen}; canRefresh: boolean; constructor(props: PropsType) { super(props); + this.userRents = null; this.state = { mascotDialogVisible: AsyncStorageManager.getBool( AsyncStorageManager.PREFERENCES.equipmentShowMascot.key, @@ -77,12 +74,17 @@ class EquipmentListScreen extends React.Component { } onScreenFocus = () => { - if (this.canRefresh && this.authRef.current != null) + if ( + this.canRefresh && + this.authRef.current && + this.authRef.current.reload + ) { this.authRef.current.reload(); + } this.canRefresh = true; }; - getRenderItem = ({item}: {item: DeviceType}): React.Node => { + getRenderItem = ({item}: {item: DeviceType}) => { const {navigation} = this.props; return ( { ); }; - getUserDeviceRentDates(item: DeviceType): [number, number] | null { + getUserDeviceRentDates(item: DeviceType): [string, string] | null { let dates = null; if (this.userRents != null) { this.userRents.forEach((device: RentedDeviceType) => { @@ -111,7 +113,7 @@ class EquipmentListScreen extends React.Component { * * @returns {*} */ - getListHeader(): React.Node { + getListHeader() { return ( { ); } - keyExtractor = (item: ClubType): string => item.id.toString(); + keyExtractor = (item: DeviceType): string => item.id.toString(); /** * Gets the main screen component with the fetched data @@ -141,15 +143,27 @@ class EquipmentListScreen extends React.Component { * @param data The data fetched from the server * @returns {*} */ - getScreen = (data: Array): React.Node => { + getScreen = ( + data: Array< + {devices: Array} | {locations: Array} | null + >, + ) => { const [allDevices, userRents] = data; - if (userRents != null) this.userRents = userRents.locations; + if (userRents) { + this.userRents = (userRents as { + locations: Array; + }).locations; + } return ( }).devices + : null + } /> ); }; @@ -166,7 +180,7 @@ class EquipmentListScreen extends React.Component { this.setState({mascotDialogVisible: false}); }; - render(): React.Node { + render() { const {props, state} = this; return ( @@ -193,7 +207,6 @@ class EquipmentListScreen extends React.Component { message={i18n.t('screens.equipment.mascotDialog.message')} icon="vote" buttons={{ - action: null, cancel: { message: i18n.t('screens.equipment.mascotDialog.button'), icon: 'check', @@ -207,4 +220,4 @@ class EquipmentListScreen extends React.Component { } } -export default withTheme(EquipmentListScreen); +export default EquipmentListScreen; diff --git a/src/screens/Amicale/Equipment/EquipmentRentScreen.js b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx similarity index 88% rename from src/screens/Amicale/Equipment/EquipmentRentScreen.js rename to src/screens/Amicale/Equipment/EquipmentRentScreen.tsx index 07501fc..08eaaa6 100644 --- a/src/screens/Amicale/Equipment/EquipmentRentScreen.js +++ b/src/screens/Amicale/Equipment/EquipmentRentScreen.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import { Button, @@ -28,13 +26,12 @@ import { Subheading, withTheme, } from 'react-native-paper'; -import {StackNavigationProp} from '@react-navigation/stack'; +import {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; import {BackHandler, View} from 'react-native'; import * as Animatable from 'react-native-animatable'; import i18n from 'i18n-js'; -import {CalendarList} from 'react-native-calendars'; +import {CalendarList, PeriodMarking} from 'react-native-calendars'; import type {DeviceType} from './EquipmentListScreen'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog'; import ErrorDialog from '../../../components/Dialogs/ErrorDialog'; import { @@ -47,43 +44,46 @@ import { } from '../../../utils/EquipmentBooking'; import ConnectionManager from '../../../managers/ConnectionManager'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; +import {MainStackParamsList} from '../../../navigation/MainNavigator'; -type PropsType = { - navigation: StackNavigationProp, - route: { - params?: { - item?: DeviceType, - }, - }, - theme: CustomThemeType, +type EquipmentRentScreenNavigationProp = StackScreenProps< + MainStackParamsList, + 'equipment-rent' +>; + +type Props = EquipmentRentScreenNavigationProp & { + navigation: StackNavigationProp; + theme: ReactNativePaper.Theme; }; export type MarkedDatesObjectType = { - [key: string]: {startingDay: boolean, endingDay: boolean, color: string}, + [key: string]: PeriodMarking; }; type StateType = { - dialogVisible: boolean, - errorDialogVisible: boolean, - markedDates: MarkedDatesObjectType, - currentError: number, + dialogVisible: boolean; + errorDialogVisible: boolean; + markedDates: MarkedDatesObjectType; + currentError: number; }; -class EquipmentRentScreen extends React.Component { +class EquipmentRentScreen extends React.Component { item: DeviceType | null; bookedDates: Array; - bookRef: {current: null | Animatable.View}; + bookRef: {current: null | (Animatable.View & View)}; canBookEquipment: boolean; lockedDates: { - [key: string]: {startingDay: boolean, endingDay: boolean, color: string}, + [key: string]: PeriodMarking; }; - constructor(props: PropsType) { + constructor(props: Props) { super(props); + this.item = null; + this.lockedDates = {}; this.state = { dialogVisible: false, errorDialogVisible: false, @@ -95,13 +95,16 @@ class EquipmentRentScreen extends React.Component { this.canBookEquipment = false; this.bookedDates = []; if (props.route.params != null) { - if (props.route.params.item != null) this.item = props.route.params.item; - else this.item = null; + if (props.route.params.item != null) { + this.item = props.route.params.item; + } else { + this.item = null; + } } const {item} = this; if (item != null) { this.lockedDates = {}; - item.booked_at.forEach((date: {begin: string, end: string}) => { + item.booked_at.forEach((date: {begin: string; end: string}) => { const range = getValidRange( new Date(date.begin), new Date(date.end), @@ -211,11 +214,11 @@ class EquipmentRentScreen extends React.Component { * @param day The day selected */ selectNewDate = (day: { - dateString: string, - day: number, - month: number, - timestamp: number, - year: number, + dateString: string; + day: number; + month: number; + timestamp: number; + year: number; }) => { const selected = new Date(day.dateString); const start = this.getBookStartDate(); @@ -229,7 +232,9 @@ class EquipmentRentScreen extends React.Component { } else if (this.bookedDates.length === 1) { this.updateSelectionRange(start, selected); this.enableBooking(); - } else this.resetSelection(); + } else { + this.resetSelection(); + } this.updateMarkedSelection(); } }; @@ -249,7 +254,7 @@ class EquipmentRentScreen extends React.Component { * Shows the book button by plying a fade animation */ showBookButton() { - if (this.bookRef.current != null) { + if (this.bookRef.current && this.bookRef.current.fadeInUp) { this.bookRef.current.fadeInUp(500); } } @@ -258,7 +263,7 @@ class EquipmentRentScreen extends React.Component { * Hides the book button by plying a fade animation */ hideBookButton() { - if (this.bookRef.current != null) { + if (this.bookRef.current && this.bookRef.current.fadeOutDown) { this.bookRef.current.fadeOutDown(500); } } @@ -271,7 +276,9 @@ class EquipmentRentScreen extends React.Component { } resetSelection() { - if (this.canBookEquipment) this.hideBookButton(); + if (this.canBookEquipment) { + this.hideBookButton(); + } this.canBookEquipment = false; this.bookedDates = []; } @@ -287,21 +294,23 @@ class EquipmentRentScreen extends React.Component { }); } - render(): React.Node { + render() { const {item, props, state} = this; const start = this.getBookStartDate(); const end = this.getBookEndDate(); let subHeadingText; - if (start == null) subHeadingText = i18n.t('screens.equipment.booking'); - else if (end != null && start.getTime() !== end.getTime()) + if (start == null) { + subHeadingText = i18n.t('screens.equipment.booking'); + } else if (end != null && start.getTime() !== end.getTime()) { subHeadingText = i18n.t('screens.equipment.bookingPeriod', { begin: getRelativeDateString(start), end: getRelativeDateString(end), }); - else + } else { subHeadingText = i18n.t('screens.equipment.bookingDay', { date: getRelativeDateString(start), }); + } if (item != null) { const isAvailable = isEquipmentAvailable(item); const firstAvailability = getFirstEquipmentAvailability(item); @@ -369,12 +378,10 @@ class EquipmentRentScreen extends React.Component { onDayPress={this.selectNewDate} // If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday. firstDay={1} - // Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates - disableAllTouchEventsForDisabledDays // Hide month navigation arrows. hideArrows={false} // Date marking style [simple/period/multi-dot/custom]. Default = 'simple' - markingType="period" + markingType={'period'} markedDates={{...this.lockedDates, ...state.markedDates}} theme={{ backgroundColor: props.theme.colors.agendaBackgroundColor, diff --git a/src/utils/EquipmentBooking.ts b/src/utils/EquipmentBooking.ts index e5e1d0f..d2cf6f6 100644 --- a/src/utils/EquipmentBooking.ts +++ b/src/utils/EquipmentBooking.ts @@ -21,6 +21,7 @@ import i18n from 'i18n-js'; import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen'; import DateManager from '../managers/DateManager'; import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen'; +import {PeriodMarking} from 'react-native-calendars'; /** * Gets the current day at midnight @@ -189,11 +190,7 @@ export function generateMarkedDates( range: Array, ): MarkedDatesObjectType { const markedDates: { - [key: string]: { - startingDay: boolean; - endingDay: boolean; - color: string; - }; + [key: string]: PeriodMarking; } = {}; for (let i = 0; i < range.length; i += 1) { const isStart = i === 0;