Update equipment screens to use TypeScript

This commit is contained in:
Arnaud Vergnet 2020-09-22 19:36:48 +02:00
parent 5977ce257b
commit 67cb96dd03
6 changed files with 196 additions and 196 deletions

View file

@ -30,7 +30,7 @@ import {
type PropsType = {
navigation: StackNavigationProp<any>;
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()) {

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
// @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<PropsType> {
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 (
<CollapsibleScrollView>
<Card style={{margin: 5}}>
<Card.Content>
<View style={{flex: 1}}>
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<Headline style={{textAlign: 'center'}}>{item.name}</Headline>
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
({i18n.t('screens.equipment.bail', {cost: item.caution})})
</Caption>
</View>
</View>
<Button
icon="check-circle-outline"
color={props.theme.colors.success}
mode="text">
{buttonText}
</Button>
<Paragraph style={{textAlign: 'center'}}>
{i18n.t('screens.equipment.bookingConfirmedMessage')}
</Paragraph>
</Card.Content>
</Card>
</CollapsibleScrollView>
);
}
return null;
}
}
export default withTheme(EquipmentConfirmScreen);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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 (
<CollapsibleScrollView>
<Card style={{margin: 5}}>
<Card.Content>
<View style={{flex: 1}}>
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<Headline style={{textAlign: 'center'}}>{item.name}</Headline>
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
({i18n.t('screens.equipment.bail', {cost: item.caution})})
</Caption>
</View>
</View>
<Button
icon="check-circle-outline"
color={theme.colors.success}
mode="text">
{buttonText}
</Button>
<Paragraph style={{textAlign: 'center'}}>
{i18n.t('screens.equipment.bookingConfirmedMessage')}
</Paragraph>
</Card.Content>
</Card>
</CollapsibleScrollView>
);
}
return null;
}
export default EquipmentConfirmScreen;

View file

@ -17,42 +17,38 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<any>;
};
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<PropsType, StateType> {
userRents: null | Array<RentedDeviceType>;
authRef: {current: null | AuthenticatedScreen};
authRef: {current: null | AuthenticatedScreen<any>};
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<PropsType, StateType> {
}
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 (
<EquipmentListItem
@ -94,7 +96,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
);
};
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<PropsType, StateType> {
*
* @returns {*}
*/
getListHeader(): React.Node {
getListHeader() {
return (
<View
style={{
@ -133,7 +135,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
);
}
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<PropsType, StateType> {
* @param data The data fetched from the server
* @returns {*}
*/
getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
getScreen = (
data: Array<
{devices: Array<DeviceType>} | {locations: Array<RentedDeviceType>} | null
>,
) => {
const [allDevices, userRents] = data;
if (userRents != null) this.userRents = userRents.locations;
if (userRents) {
this.userRents = (userRents as {
locations: Array<RentedDeviceType>;
}).locations;
}
return (
<CollapsibleFlatList
keyExtractor={this.keyExtractor}
renderItem={this.getRenderItem}
ListHeaderComponent={this.getListHeader()}
data={allDevices != null ? allDevices.devices : null}
data={
allDevices
? (allDevices as {devices: Array<DeviceType>}).devices
: null
}
/>
);
};
@ -166,7 +180,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
this.setState({mascotDialogVisible: false});
};
render(): React.Node {
render() {
const {props, state} = this;
return (
<View style={{flex: 1}}>
@ -193,7 +207,6 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
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<PropsType, StateType> {
}
}
export default withTheme(EquipmentListScreen);
export default EquipmentListScreen;

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<any>;
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<PropsType, StateType> {
class EquipmentRentScreen extends React.Component<Props, StateType> {
item: DeviceType | null;
bookedDates: Array<string>;
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<PropsType, StateType> {
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<PropsType, StateType> {
* @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<PropsType, StateType> {
} 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<PropsType, StateType> {
* 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<PropsType, StateType> {
* 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<PropsType, StateType> {
}
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<PropsType, StateType> {
});
}
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<PropsType, StateType> {
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,

View file

@ -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<string>,
): 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;