Improve equipment booking components to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-04 10:57:19 +02:00
parent 70365136ac
commit 11b5f2ac71
5 changed files with 871 additions and 863 deletions

View file

@ -2,46 +2,48 @@
import * as React from 'react';
import {Avatar, List, withTheme} from 'react-native-paper';
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {Device} from "../../../screens/Amicale/Equipment/EquipmentListScreen";
import i18n from "i18n-js";
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen';
import {
getFirstEquipmentAvailability,
getRelativeDateString,
isEquipmentAvailable
} from "../../../utils/EquipmentBooking";
import {StackNavigationProp} from "@react-navigation/stack";
isEquipmentAvailable,
} from '../../../utils/EquipmentBooking';
type Props = {
type PropsType = {
navigation: StackNavigationProp,
userDeviceRentDates: [string, string],
item: Device,
item: DeviceType,
height: number,
theme: CustomTheme,
}
};
class EquipmentListItem extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
class EquipmentListItem extends React.Component<PropsType> {
shouldComponentUpdate(nextProps: PropsType): boolean {
const {userDeviceRentDates} = this.props;
return nextProps.userDeviceRentDates !== userDeviceRentDates;
}
render() {
const colors = this.props.theme.colors;
const item = this.props.item;
const userDeviceRentDates = this.props.userDeviceRentDates;
render(): React.Node {
const {item, userDeviceRentDates, navigation, height, theme} = this.props;
const isRented = userDeviceRentDates != null;
const isAvailable = isEquipmentAvailable(item);
const firstAvailability = getFirstEquipmentAvailability(item);
let onPress;
if (isRented)
onPress = () => this.props.navigation.navigate("equipment-confirm", {
item: item,
dates: userDeviceRentDates
onPress = () => {
navigation.navigate('equipment-confirm', {
item,
dates: userDeviceRentDates,
});
};
else
onPress = () => this.props.navigation.navigate("equipment-rent", {item: item});
onPress = () => {
navigation.navigate('equipment-rent', {item});
};
let description;
if (isRented) {
@ -50,58 +52,57 @@ class EquipmentListItem extends React.Component<Props> {
if (start.getTime() !== end.getTime())
description = i18n.t('screens.equipment.bookingPeriod', {
begin: getRelativeDateString(start),
end: getRelativeDateString(end)
end: getRelativeDateString(end),
});
else
description = i18n.t('screens.equipment.bookingDay', {
date: getRelativeDateString(start)
date: getRelativeDateString(start),
});
} else if (isAvailable)
description = i18n.t('screens.equipment.bail', {cost: item.caution});
else
description = i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)});
description = i18n.t('screens.equipment.available', {
date: getRelativeDateString(firstAvailability),
});
let icon;
if (isRented)
icon = "bookmark-check";
else if (isAvailable)
icon = "check-circle-outline";
else
icon = "update";
if (isRented) icon = 'bookmark-check';
else if (isAvailable) icon = 'check-circle-outline';
else icon = 'update';
let color;
if (isRented)
color = colors.warning;
else if (isAvailable)
color = colors.success;
else
color = colors.primary;
if (isRented) color = theme.colors.warning;
else if (isAvailable) color = theme.colors.success;
else color = theme.colors.primary;
return (
<List.Item
title={item.name}
description={description}
onPress={onPress}
left={(props) => <Avatar.Icon
{...props}
left={({size}: {size: number}): React.Node => (
<Avatar.Icon
size={size}
style={{
backgroundColor: 'transparent',
}}
icon={icon}
color={color}
/>}
right={(props) => <Avatar.Icon
{...props}
/>
)}
right={(): React.Node => (
<Avatar.Icon
style={{
marginTop: 'auto',
marginBottom: 'auto',
backgroundColor: 'transparent',
}}
size={48}
icon={"chevron-right"}
/>}
icon="chevron-right"
/>
)}
style={{
height: this.props.height,
height,
justifyContent: 'center',
}}
/>

View file

@ -1,68 +1,79 @@
// @flow
import * as React from 'react';
import {Button, Caption, Card, Headline, Paragraph, withTheme} from 'react-native-paper';
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {Device} from "./EquipmentListScreen";
import {View} from "react-native";
import i18n from "i18n-js";
import {getRelativeDateString} from "../../../utils/EquipmentBooking";
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
import {
Button,
Caption,
Card,
Headline,
Paragraph,
withTheme,
} from 'react-native-paper';
import {View} from 'react-native';
import i18n from 'i18n-js';
import type {CustomTheme} from '../../../managers/ThemeManager';
import type {DeviceType} from './EquipmentListScreen';
import {getRelativeDateString} from '../../../utils/EquipmentBooking';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
type Props = {
navigation: StackNavigationProp,
type PropsType = {
route: {
params?: {
item?: Device,
dates: [string, string]
item?: DeviceType,
dates: [string, string],
},
},
theme: CustomTheme,
}
};
class EquipmentConfirmScreen extends React.Component<PropsType> {
item: DeviceType | null;
class EquipmentConfirmScreen extends React.Component<Props> {
item: Device | null;
dates: [string, string] | null;
constructor(props: Props) {
constructor(props: PropsType) {
super(props);
if (this.props.route.params != null) {
if (this.props.route.params.item != null)
this.item = this.props.route.params.item;
else
this.item = null;
if (this.props.route.params.dates != null)
this.dates = this.props.route.params.dates;
else
this.dates = null;
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() {
const item = this.item;
const dates = this.dates;
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",
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<Headline style={{textAlign: "center"}}>
{item.name}
</Headline>
<Caption style={{
textAlign: "center",
<Headline style={{textAlign: 'center'}}>{item.name}</Headline>
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
@ -71,35 +82,21 @@ class EquipmentConfirmScreen extends React.Component<Props> {
</View>
</View>
<Button
icon={"check-circle-outline"}
color={this.props.theme.colors.success}
mode="text"
>
{
start == null
? i18n.t('screens.equipment.booking')
: end != null && start.getTime() !== end.getTime()
? i18n.t('screens.equipment.bookingPeriod', {
begin: getRelativeDateString(start),
end: getRelativeDateString(end)
})
: i18n.t('screens.equipment.bookingDay', {
date: getRelativeDateString(start)
})
}
icon="check-circle-outline"
color={props.theme.colors.success}
mode="text">
{buttonText}
</Button>
<Paragraph style={{textAlign: "center"}}>
{i18n.t("screens.equipment.bookingConfirmedMessage")}
<Paragraph style={{textAlign: 'center'}}>
{i18n.t('screens.equipment.bookingConfirmedMessage')}
</Paragraph>
</Card.Content>
</Card>
</CollapsibleScrollView>
);
} else
return null;
}
return null;
}
}
export default withTheme(EquipmentConfirmScreen);

View file

@ -1,61 +1,62 @@
// @flow
import * as React from 'react';
import {View} from "react-native";
import {View} from 'react-native';
import {Button, withTheme} from 'react-native-paper';
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import i18n from "i18n-js";
import type {club} 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 {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 Props = {
type PropsType = {
navigation: StackNavigationProp,
theme: CustomTheme,
}
};
type State = {
type StateType = {
mascotDialogVisible: boolean,
}
};
export type Device = {
export type DeviceType = {
id: number,
name: string,
caution: number,
booked_at: Array<{ begin: string, end: string }>,
booked_at: Array<{begin: string, end: string}>,
};
export type RentedDevice = {
export type RentedDeviceType = {
device_id: number,
device_name: string,
begin: string,
end: string,
}
};
const LIST_ITEM_HEIGHT = 64;
class EquipmentListScreen extends React.Component<Props, State> {
class EquipmentListScreen extends React.Component<PropsType, StateType> {
data: Array<DeviceType>;
state = {
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key),
}
userRents: Array<RentedDeviceType>;
data: Array<Device>;
userRents: Array<RentedDevice>;
authRef: {current: null | AuthenticatedScreen};
authRef: { current: null | AuthenticatedScreen };
canRefresh: boolean;
constructor(props: Props) {
constructor(props: PropsType) {
super(props);
this.state = {
mascotDialogVisible: AsyncStorageManager.getBool(
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
),
};
this.canRefresh = false;
this.authRef = React.createRef();
this.props.navigation.addListener('focus', this.onScreenFocus);
props.navigation.addListener('focus', this.onScreenFocus);
}
onScreenFocus = () => {
@ -64,25 +65,25 @@ class EquipmentListScreen extends React.Component<Props, State> {
this.canRefresh = true;
};
getRenderItem = ({item}: { item: Device }) => {
getRenderItem = ({item}: {item: DeviceType}): React.Node => {
const {navigation} = this.props;
return (
<EquipmentListItem
navigation={this.props.navigation}
navigation={navigation}
item={item}
userDeviceRentDates={this.getUserDeviceRentDates(item)}
height={LIST_ITEM_HEIGHT}/>
height={LIST_ITEM_HEIGHT}
/>
);
};
getUserDeviceRentDates(item: Device) {
getUserDeviceRentDates(item: DeviceType): [number, number] | null {
let dates = null;
for (let i = 0; i < this.userRents.length; i++) {
let device = this.userRents[i];
this.userRents.forEach((device: RentedDeviceType) => {
if (item.id === device.device_id) {
dates = [device.begin, device.end];
break;
}
}
});
return dates;
}
@ -91,28 +92,29 @@ class EquipmentListScreen extends React.Component<Props, State> {
*
* @returns {*}
*/
getListHeader() {
getListHeader(): React.Node {
return (
<View style={{
width: "100%",
<View
style={{
width: '100%',
marginTop: 10,
marginBottom: 10,
}}>
<Button
mode={"contained"}
icon={"help-circle"}
mode="contained"
icon="help-circle"
onPress={this.showMascotDialog}
style={{
marginRight: "auto",
marginLeft: "auto",
marginRight: 'auto',
marginLeft: 'auto',
}}>
{i18n.t("screens.equipment.mascotDialog.title")}
{i18n.t('screens.equipment.mascotDialog.title')}
</Button>
</View>
);
}
keyExtractor = (item: club) => item.id.toString();
keyExtractor = (item: ClubType): string => item.id.toString();
/**
* Gets the main screen component with the fetched data
@ -120,16 +122,14 @@ class EquipmentListScreen extends React.Component<Props, State> {
* @param data The data fetched from the server
* @returns {*}
*/
getScreen = (data: Array<{ [key: string]: any } | null>) => {
getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
if (data[0] != null) {
const fetchedData = data[0];
if (fetchedData != null)
this.data = fetchedData["devices"];
if (fetchedData != null) this.data = fetchedData.devices;
}
if (data[1] != null) {
const fetchedData = data[1];
if (fetchedData != null)
this.userRents = fetchedData["locations"];
if (fetchedData != null) this.userRents = fetchedData.locations;
}
return (
<CollapsibleFlatList
@ -138,23 +138,27 @@ class EquipmentListScreen extends React.Component<Props, State> {
ListHeaderComponent={this.getListHeader()}
data={this.data}
/>
)
);
};
showMascotDialog = () => {
this.setState({mascotDialogVisible: true})
this.setState({mascotDialogVisible: true});
};
hideMascotDialog = () => {
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key, false);
this.setState({mascotDialogVisible: false})
AsyncStorageManager.set(
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
false,
);
this.setState({mascotDialogVisible: false});
};
render() {
render(): React.Node {
const {props, state} = this;
return (
<View style={{flex: 1}}>
<AuthenticatedScreen
{...this.props}
navigation={props.navigation}
ref={this.authRef}
requests={[
{
@ -166,22 +170,22 @@ class EquipmentListScreen extends React.Component<Props, State> {
link: 'location/my',
params: {},
mandatory: false,
}
},
]}
renderFunction={this.getScreen}
/>
<MascotPopup
visible={this.state.mascotDialogVisible}
title={i18n.t("screens.equipment.mascotDialog.title")}
message={i18n.t("screens.equipment.mascotDialog.message")}
icon={"vote"}
visible={state.mascotDialogVisible}
title={i18n.t('screens.equipment.mascotDialog.title')}
message={i18n.t('screens.equipment.mascotDialog.message')}
icon="vote"
buttons={{
action: null,
cancel: {
message: i18n.t("screens.equipment.mascotDialog.button"),
icon: "check",
message: i18n.t('screens.equipment.mascotDialog.button'),
icon: 'check',
onPress: this.hideMascotDialog,
}
},
}}
emotion={MASCOT_STYLE.WINK}
/>

View file

@ -1,111 +1,118 @@
// @flow
import * as React from 'react';
import {Button, Caption, Card, Headline, Subheading, withTheme} from 'react-native-paper';
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../../managers/ThemeManager";
import type {Device} from "./EquipmentListScreen";
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 LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
import {
Button,
Caption,
Card,
Headline,
Subheading,
withTheme,
} from 'react-native-paper';
import {StackNavigationProp} 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 type {DeviceType} from './EquipmentListScreen';
import type {CustomTheme} from '../../../managers/ThemeManager';
import LoadingConfirmDialog from '../../../components/Dialogs/LoadingConfirmDialog';
import ErrorDialog from '../../../components/Dialogs/ErrorDialog';
import {
generateMarkedDates,
getFirstEquipmentAvailability,
getISODate,
getRelativeDateString,
getValidRange,
isEquipmentAvailable
} from "../../../utils/EquipmentBooking";
import ConnectionManager from "../../../managers/ConnectionManager";
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
isEquipmentAvailable,
} from '../../../utils/EquipmentBooking';
import ConnectionManager from '../../../managers/ConnectionManager';
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
type Props = {
type PropsType = {
navigation: StackNavigationProp,
route: {
params?: {
item?: Device,
item?: DeviceType,
},
},
theme: CustomTheme,
}
};
type State = {
export type MarkedDatesObjectType = {
[key: string]: {startingDay: boolean, endingDay: boolean, color: string},
};
type StateType = {
dialogVisible: boolean,
errorDialogVisible: boolean,
markedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } },
markedDates: MarkedDatesObjectType,
currentError: number,
}
};
class EquipmentRentScreen extends React.Component<Props, State> {
class EquipmentRentScreen extends React.Component<PropsType, StateType> {
item: DeviceType | null;
state = {
bookedDates: Array<string>;
bookRef: {current: null | Animatable.View};
canBookEquipment: boolean;
lockedDates: {
[key: string]: {startingDay: boolean, endingDay: boolean, color: string},
};
constructor(props: PropsType) {
super(props);
this.state = {
dialogVisible: false,
errorDialogVisible: false,
markedDates: {},
currentError: 0,
}
item: Device | null;
bookedDates: Array<string>;
bookRef: { current: null | Animatable.View }
canBookEquipment: boolean;
lockedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } }
constructor(props: Props) {
super(props);
};
this.resetSelection();
this.bookRef = React.createRef();
this.canBookEquipment = false;
this.bookedDates = [];
if (this.props.route.params != null) {
if (this.props.route.params.item != null)
this.item = this.props.route.params.item;
else
this.item = null;
if (props.route.params != null) {
if (props.route.params.item != null) this.item = props.route.params.item;
else this.item = null;
}
const item = this.item;
const {item} = this;
if (item != null) {
this.lockedDates = {};
for (let i = 0; i < item.booked_at.length; i++) {
const range = getValidRange(new Date(item.booked_at[i].begin), new Date(item.booked_at[i].end), null);
item.booked_at.forEach((date: {begin: string, end: string}) => {
const range = getValidRange(
new Date(date.begin),
new Date(date.end),
null,
);
this.lockedDates = {
...this.lockedDates,
...generateMarkedDates(
false,
this.props.theme,
range
)
...generateMarkedDates(false, props.theme, range),
};
});
}
}
}
/**
* Captures focus and blur events to hook on android back button
*/
componentDidMount() {
this.props.navigation.addListener(
'focus',
() =>
const {navigation} = this.props;
navigation.addListener('focus', () => {
BackHandler.addEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid
)
this.onBackButtonPressAndroid,
);
this.props.navigation.addListener(
'blur',
() =>
});
navigation.addListener('blur', () => {
BackHandler.removeEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid
)
this.onBackButtonPressAndroid,
);
});
}
/**
@ -113,26 +120,88 @@ class EquipmentRentScreen extends React.Component<Props, State> {
*
* @return {boolean}
*/
onBackButtonPressAndroid = () => {
onBackButtonPressAndroid = (): boolean => {
if (this.bookedDates.length > 0) {
this.resetSelection();
this.updateMarkedSelection();
return true;
} else
}
return false;
};
onDialogDismiss = () => {
this.setState({dialogVisible: false});
};
onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false});
};
/**
* Sends the selected data to the server and waits for a response.
* If the request is a success, navigate to the recap screen.
* If it is an error, display the error to the user.
*
* @returns {Promise<void>}
*/
onDialogAccept = (): Promise<void> => {
return new Promise((resolve: () => void) => {
const {item, props} = this;
const start = this.getBookStartDate();
const end = this.getBookEndDate();
if (item != null && start != null && end != null) {
ConnectionManager.getInstance()
.authenticatedRequest('location/booking', {
device: item.id,
begin: getISODate(start),
end: getISODate(end),
})
.then(() => {
this.onDialogDismiss();
props.navigation.replace('equipment-confirm', {
item: this.item,
dates: [getISODate(start), getISODate(end)],
});
resolve();
})
.catch((error: number) => {
this.onDialogDismiss();
this.showErrorDialog(error);
resolve();
});
} else {
this.onDialogDismiss();
resolve();
}
});
};
getBookStartDate(): Date | null {
return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
}
getBookEndDate(): Date | null {
const {length} = this.bookedDates;
return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
}
/**
* Selects a new date on the calendar.
* If both start and end dates are already selected, unselect all.
*
* @param day The day selected
*/
selectNewDate = (day: { dateString: string, day: number, month: number, timestamp: number, year: number }) => {
selectNewDate = (day: {
dateString: string,
day: number,
month: number,
timestamp: number,
year: number,
}) => {
const selected = new Date(day.dateString);
const start = this.getBookStartDate();
if (!(this.lockedDates.hasOwnProperty(day.dateString))) {
if (!this.lockedDates[day.dateString] != null) {
if (start === null) {
this.updateSelectionRange(selected, selected);
this.enableBooking();
@ -141,39 +210,21 @@ class EquipmentRentScreen extends React.Component<Props, State> {
} else if (this.bookedDates.length === 1) {
this.updateSelectionRange(start, selected);
this.enableBooking();
} else
this.resetSelection();
} else this.resetSelection();
this.updateMarkedSelection();
}
}
};
updateSelectionRange(start: Date, end: Date) {
this.bookedDates = getValidRange(start, end, this.item);
}
updateMarkedSelection() {
showErrorDialog = (error: number) => {
this.setState({
markedDates: generateMarkedDates(
true,
this.props.theme,
this.bookedDates
),
errorDialogVisible: true,
currentError: error,
});
}
};
enableBooking() {
if (!this.canBookEquipment) {
this.showBookButton();
this.canBookEquipment = true;
}
}
resetSelection() {
if (this.canBookEquipment)
this.hideBookButton();
this.canBookEquipment = false;
this.bookedDates = [];
}
showDialog = () => {
this.setState({dialogVisible: true});
};
/**
* Shows the book button by plying a fade animation
@ -193,84 +244,45 @@ class EquipmentRentScreen extends React.Component<Props, State> {
}
}
showDialog = () => {
this.setState({dialogVisible: true});
enableBooking() {
if (!this.canBookEquipment) {
this.showBookButton();
this.canBookEquipment = true;
}
}
showErrorDialog = (error: number) => {
resetSelection() {
if (this.canBookEquipment) this.hideBookButton();
this.canBookEquipment = false;
this.bookedDates = [];
}
updateSelectionRange(start: Date, end: Date) {
this.bookedDates = getValidRange(start, end, this.item);
}
updateMarkedSelection() {
const {theme} = this.props;
this.setState({
errorDialogVisible: true,
currentError: error,
markedDates: generateMarkedDates(true, theme, this.bookedDates),
});
}
onDialogDismiss = () => {
this.setState({dialogVisible: false});
}
onErrorDialogDismiss = () => {
this.setState({errorDialogVisible: false});
}
/**
* Sends the selected data to the server and waits for a response.
* If the request is a success, navigate to the recap screen.
* If it is an error, display the error to the user.
*
* @returns {Promise<R>}
*/
onDialogAccept = () => {
return new Promise((resolve) => {
const item = this.item;
render(): React.Node {
const {item, props, state} = this;
const start = this.getBookStartDate();
const end = this.getBookEndDate();
if (item != null && start != null && end != null) {
console.log({
"device": item.id,
"begin": getISODate(start),
"end": getISODate(end),
})
ConnectionManager.getInstance().authenticatedRequest(
"location/booking",
{
"device": item.id,
"begin": getISODate(start),
"end": getISODate(end),
})
.then(() => {
this.onDialogDismiss();
this.props.navigation.replace("equipment-confirm", {
item: this.item,
dates: [getISODate(start), getISODate(end)]
let subHeadingText;
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),
});
resolve();
})
.catch((error: number) => {
this.onDialogDismiss();
this.showErrorDialog(error);
resolve();
else
i18n.t('screens.equipment.bookingDay', {
date: getRelativeDateString(start),
});
} else {
this.onDialogDismiss();
resolve();
}
});
}
getBookStartDate() {
return this.bookedDates.length > 0 ? new Date(this.bookedDates[0]) : null;
}
getBookEndDate() {
const length = this.bookedDates.length;
return length > 0 ? new Date(this.bookedDates[length - 1]) : null;
}
render() {
const item = this.item;
const start = this.getBookStartDate();
const end = this.getBookEndDate();
if (item != null) {
const isAvailable = isEquipmentAvailable(item);
const firstAvailability = getFirstEquipmentAvailability(item);
@ -280,17 +292,19 @@ class EquipmentRentScreen extends React.Component<Props, State> {
<Card style={{margin: 5}}>
<Card.Content>
<View style={{flex: 1}}>
<View style={{
marginLeft: "auto",
marginRight: "auto",
flexDirection: "row",
flexWrap: "wrap",
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
flexDirection: 'row',
flexWrap: 'wrap',
}}>
<Headline style={{textAlign: "center"}}>
<Headline style={{textAlign: 'center'}}>
{item.name}
</Headline>
<Caption style={{
textAlign: "center",
<Caption
style={{
textAlign: 'center',
lineHeight: 35,
marginLeft: 10,
}}>
@ -300,30 +314,24 @@ class EquipmentRentScreen extends React.Component<Props, State> {
</View>
<Button
icon={isAvailable ? "check-circle-outline" : "update"}
color={isAvailable ? this.props.theme.colors.success : this.props.theme.colors.primary}
mode="text"
>
{i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)})}
</Button>
<Subheading style={{
textAlign: "center",
marginBottom: 10,
minHeight: 50
}}>
{
start == null
? i18n.t('screens.equipment.booking')
: end != null && start.getTime() !== end.getTime()
? i18n.t('screens.equipment.bookingPeriod', {
begin: getRelativeDateString(start),
end: getRelativeDateString(end)
})
: i18n.t('screens.equipment.bookingDay', {
date: getRelativeDateString(start)
})
icon={isAvailable ? 'check-circle-outline' : 'update'}
color={
isAvailable
? props.theme.colors.success
: props.theme.colors.primary
}
mode="text">
{i18n.t('screens.equipment.available', {
date: getRelativeDateString(firstAvailability),
})}
</Button>
<Subheading
style={{
textAlign: 'center',
marginBottom: 10,
minHeight: 50,
}}>
{subHeadingText}
</Subheading>
</Card.Content>
</Card>
@ -335,35 +343,34 @@ class EquipmentRentScreen extends React.Component<Props, State> {
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={3}
// Enable horizontal scrolling, default = false
horizontal={true}
horizontal
// Enable paging on horizontal, default = false
pagingEnabled={true}
pagingEnabled
// Handler which gets executed on day press. Default = undefined
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={true}
disableAllTouchEventsForDisabledDays
// Hide month navigation arrows.
hideArrows={false}
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
markingType={'period'}
markedDates={{...this.lockedDates, ...this.state.markedDates}}
markingType="period"
markedDates={{...this.lockedDates, ...state.markedDates}}
theme={{
backgroundColor: this.props.theme.colors.agendaBackgroundColor,
calendarBackground: this.props.theme.colors.background,
textSectionTitleColor: this.props.theme.colors.agendaDayTextColor,
selectedDayBackgroundColor: this.props.theme.colors.primary,
backgroundColor: props.theme.colors.agendaBackgroundColor,
calendarBackground: props.theme.colors.background,
textSectionTitleColor: props.theme.colors.agendaDayTextColor,
selectedDayBackgroundColor: props.theme.colors.primary,
selectedDayTextColor: '#ffffff',
todayTextColor: this.props.theme.colors.text,
dayTextColor: this.props.theme.colors.text,
textDisabledColor: this.props.theme.colors.agendaDayTextColor,
dotColor: this.props.theme.colors.primary,
todayTextColor: props.theme.colors.text,
dayTextColor: props.theme.colors.text,
textDisabledColor: props.theme.colors.agendaDayTextColor,
dotColor: props.theme.colors.primary,
selectedDotColor: '#ffffff',
arrowColor: this.props.theme.colors.primary,
monthTextColor: this.props.theme.colors.text,
indicatorColor: this.props.theme.colors.primary,
arrowColor: props.theme.colors.primary,
monthTextColor: props.theme.colors.text,
indicatorColor: props.theme.colors.primary,
textDayFontFamily: 'monospace',
textMonthFontFamily: 'monospace',
textDayHeaderFontFamily: 'monospace',
@ -379,15 +386,14 @@ class EquipmentRentScreen extends React.Component<Props, State> {
height: 34,
width: 34,
alignItems: 'center',
}
}
},
},
}}
style={{marginBottom: 50}}
/>
</CollapsibleScrollView>
<LoadingConfirmDialog
visible={this.state.dialogVisible}
visible={state.dialogVisible}
onDismiss={this.onDialogDismiss}
onAccept={this.onDialogAccept}
title={i18n.t('screens.equipment.dialogTitle')}
@ -396,46 +402,40 @@ class EquipmentRentScreen extends React.Component<Props, State> {
/>
<ErrorDialog
visible={this.state.errorDialogVisible}
visible={state.errorDialogVisible}
onDismiss={this.onErrorDialogDismiss}
errorCode={this.state.currentError}
errorCode={state.currentError}
/>
<Animatable.View
ref={this.bookRef}
style={{
position: "absolute",
position: 'absolute',
bottom: 0,
left: 0,
width: "100%",
width: '100%',
flex: 1,
transform: [
{translateY: 100},
]
transform: [{translateY: 100}],
}}>
<Button
icon="bookmark-check"
mode="contained"
onPress={this.showDialog}
style={{
width: "80%",
width: '80%',
flex: 1,
marginLeft: "auto",
marginRight: "auto",
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20,
borderRadius: 10
}}
>
borderRadius: 10,
}}>
{i18n.t('screens.equipment.bookButton')}
</Button>
</Animatable.View>
</View>
)
} else
return <View/>;
);
}
return null;
}
}
export default withTheme(EquipmentRentScreen);

View file

@ -1,17 +1,18 @@
// @flow
import type {Device} from "../screens/Amicale/Equipment/EquipmentListScreen";
import i18n from "i18n-js";
import DateManager from "../managers/DateManager";
import type {CustomTheme} from "../managers/ThemeManager";
import i18n from 'i18n-js';
import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen';
import DateManager from '../managers/DateManager';
import type {CustomTheme} from '../managers/ThemeManager';
import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen';
/**
* Gets the current day at midnight
*
* @returns {Date}
*/
export function getCurrentDay() {
let today = new Date(Date.now());
export function getCurrentDay(): Date {
const today = new Date(Date.now());
today.setUTCHours(0, 0, 0, 0);
return today;
}
@ -22,8 +23,8 @@ export function getCurrentDay() {
* @param date The date to recover the ISO format from
* @returns {*}
*/
export function getISODate(date: Date) {
return date.toISOString().split("T")[0];
export function getISODate(date: Date): string {
return date.toISOString().split('T')[0];
}
/**
@ -32,17 +33,15 @@ export function getISODate(date: Date) {
* @param item
* @returns {boolean}
*/
export function isEquipmentAvailable(item: Device) {
export function isEquipmentAvailable(item: DeviceType): boolean {
let isAvailable = true;
const today = getCurrentDay();
const dates = item.booked_at;
for (let i = 0; i < dates.length; i++) {
const start = new Date(dates[i].begin);
const end = new Date(dates[i].end);
isAvailable = today < start || today > end;
if (!isAvailable)
break;
}
dates.forEach((date: {begin: string, end: string}) => {
const start = new Date(date.begin);
const end = new Date(date.end);
if (!(today < start || today > end)) isAvailable = false;
});
return isAvailable;
}
@ -52,16 +51,15 @@ export function isEquipmentAvailable(item: Device) {
* @param item
* @returns {Date}
*/
export function getFirstEquipmentAvailability(item: Device) {
export function getFirstEquipmentAvailability(item: DeviceType): Date {
let firstAvailability = getCurrentDay();
const dates = item.booked_at;
for (let i = 0; i < dates.length; i++) {
const start = new Date(dates[i].begin);
let end = new Date(dates[i].end);
dates.forEach((date: {begin: string, end: string}) => {
const start = new Date(date.begin);
const end = new Date(date.end);
end.setDate(end.getDate() + 1);
if (firstAvailability >= start)
firstAvailability = end;
}
if (firstAvailability >= start) firstAvailability = end;
});
return firstAvailability;
}
@ -70,7 +68,7 @@ export function getFirstEquipmentAvailability(item: Device) {
*
* @param date The date to translate
*/
export function getRelativeDateString(date: Date) {
export function getRelativeDateString(date: Date): string {
const today = getCurrentDay();
const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
@ -80,7 +78,7 @@ export function getRelativeDateString(date: Date) {
translatedString = i18n.t('screens.equipment.otherYear', {
date: date.getDate(),
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
year: date.getFullYear()
year: date.getFullYear(),
});
else if (monthDelta > 0)
translatedString = i18n.t('screens.equipment.otherMonth', {
@ -111,13 +109,17 @@ export function getRelativeDateString(date: Date) {
* @param item Item containing booked dates to look for
* @returns {[string]}
*/
export function getValidRange(start: Date, end: Date, item: Device | null) {
let direction = start <= end ? 1 : -1;
export function getValidRange(
start: Date,
end: Date,
item: DeviceType | null,
): Array<string> {
const direction = start <= end ? 1 : -1;
let limit = new Date(end);
limit.setDate(limit.getDate() + direction); // Limit is excluded, but we want to include range end
if (item != null) {
if (direction === 1) {
for (let i = 0; i < item.booked_at.length; i++) {
for (let i = 0; i < item.booked_at.length; i += 1) {
const bookLimit = new Date(item.booked_at[i].begin);
if (start < bookLimit && limit > bookLimit) {
limit = bookLimit;
@ -125,7 +127,7 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
}
}
} else {
for (let i = item.booked_at.length - 1; i >= 0; i--) {
for (let i = item.booked_at.length - 1; i >= 0; i -= 1) {
const bookLimit = new Date(item.booked_at[i].end);
if (start > bookLimit && limit < bookLimit) {
limit = bookLimit;
@ -135,14 +137,14 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
}
}
let validRange = [];
let date = new Date(start);
while ((direction === 1 && date < limit) || (direction === -1 && date > limit)) {
if (direction === 1)
validRange.push(getISODate(date));
else
validRange.unshift(getISODate(date));
const validRange = [];
const date = new Date(start);
while (
(direction === 1 && date < limit) ||
(direction === -1 && date > limit)
) {
if (direction === 1) validRange.push(getISODate(date));
else validRange.unshift(getISODate(date));
date.setDate(date.getDate() + direction);
}
return validRange;
@ -157,19 +159,23 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
* @param range The range to mark dates for
* @returns {{}}
*/
export function generateMarkedDates(isSelection: boolean, theme: CustomTheme, range: Array<string>) {
let markedDates = {}
for (let i = 0; i < range.length; i++) {
export function generateMarkedDates(
isSelection: boolean,
theme: CustomTheme,
range: Array<string>,
): MarkedDatesObjectType {
const markedDates = {};
for (let i = 0; i < range.length; i += 1) {
const isStart = i === 0;
const isEnd = i === range.length - 1;
let color;
if (isSelection && (isStart || isEnd)) color = theme.colors.primary;
else if (isSelection) color = theme.colors.danger;
else color = theme.colors.textDisabled;
markedDates[range[i]] = {
startingDay: isStart,
endingDay: isEnd,
color: isSelection
? isStart || isEnd
? theme.colors.primary
: theme.colors.danger
: theme.colors.textDisabled
color,
};
}
return markedDates;