forked from vergnet/application-amicale
Improve equipment booking components to match linter
This commit is contained in:
parent
70365136ac
commit
11b5f2ac71
5 changed files with 871 additions and 863 deletions
|
@ -2,111 +2,112 @@
|
|||
|
||||
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";
|
||||
getFirstEquipmentAvailability,
|
||||
getRelativeDateString,
|
||||
isEquipmentAvailable,
|
||||
} from '../../../utils/EquipmentBooking';
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
userDeviceRentDates: [string, string],
|
||||
item: Device,
|
||||
height: number,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
userDeviceRentDates: [string, string],
|
||||
item: DeviceType,
|
||||
height: number,
|
||||
theme: CustomTheme,
|
||||
};
|
||||
|
||||
class EquipmentListItem extends React.Component<Props> {
|
||||
class EquipmentListItem extends React.Component<PropsType> {
|
||||
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||
const {userDeviceRentDates} = this.props;
|
||||
return nextProps.userDeviceRentDates !== userDeviceRentDates;
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: Props): boolean {
|
||||
return nextProps.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);
|
||||
|
||||
render() {
|
||||
const colors = this.props.theme.colors;
|
||||
const item = this.props.item;
|
||||
const userDeviceRentDates = this.props.userDeviceRentDates;
|
||||
const isRented = userDeviceRentDates != null;
|
||||
const isAvailable = isEquipmentAvailable(item);
|
||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||
let onPress;
|
||||
if (isRented)
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-confirm', {
|
||||
item,
|
||||
dates: userDeviceRentDates,
|
||||
});
|
||||
};
|
||||
else
|
||||
onPress = () => {
|
||||
navigation.navigate('equipment-rent', {item});
|
||||
};
|
||||
|
||||
let onPress;
|
||||
if (isRented)
|
||||
onPress = () => this.props.navigation.navigate("equipment-confirm", {
|
||||
item: item,
|
||||
dates: userDeviceRentDates
|
||||
});
|
||||
else
|
||||
onPress = () => this.props.navigation.navigate("equipment-rent", {item: item});
|
||||
let description;
|
||||
if (isRented) {
|
||||
const start = new Date(userDeviceRentDates[0]);
|
||||
const end = new Date(userDeviceRentDates[1]);
|
||||
if (start.getTime() !== end.getTime())
|
||||
description = i18n.t('screens.equipment.bookingPeriod', {
|
||||
begin: getRelativeDateString(start),
|
||||
end: getRelativeDateString(end),
|
||||
});
|
||||
else
|
||||
description = i18n.t('screens.equipment.bookingDay', {
|
||||
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),
|
||||
});
|
||||
|
||||
let description;
|
||||
if (isRented) {
|
||||
const start = new Date(userDeviceRentDates[0]);
|
||||
const end = new Date(userDeviceRentDates[1]);
|
||||
if (start.getTime() !== end.getTime())
|
||||
description = i18n.t('screens.equipment.bookingPeriod', {
|
||||
begin: getRelativeDateString(start),
|
||||
end: getRelativeDateString(end)
|
||||
});
|
||||
else
|
||||
description = i18n.t('screens.equipment.bookingDay', {
|
||||
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)});
|
||||
let icon;
|
||||
if (isRented) icon = 'bookmark-check';
|
||||
else if (isAvailable) icon = 'check-circle-outline';
|
||||
else icon = 'update';
|
||||
|
||||
let icon;
|
||||
if (isRented)
|
||||
icon = "bookmark-check";
|
||||
else if (isAvailable)
|
||||
icon = "check-circle-outline";
|
||||
else
|
||||
icon = "update";
|
||||
let color;
|
||||
if (isRented) color = theme.colors.warning;
|
||||
else if (isAvailable) color = theme.colors.success;
|
||||
else color = theme.colors.primary;
|
||||
|
||||
let color;
|
||||
if (isRented)
|
||||
color = colors.warning;
|
||||
else if (isAvailable)
|
||||
color = colors.success;
|
||||
else
|
||||
color = colors.primary;
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={description}
|
||||
onPress={onPress}
|
||||
left={(props) => <Avatar.Icon
|
||||
{...props}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
icon={icon}
|
||||
color={color}
|
||||
/>}
|
||||
right={(props) => <Avatar.Icon
|
||||
{...props}
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
size={48}
|
||||
icon={"chevron-right"}
|
||||
/>}
|
||||
style={{
|
||||
height: this.props.height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<List.Item
|
||||
title={item.name}
|
||||
description={description}
|
||||
onPress={onPress}
|
||||
left={({size}: {size: number}): React.Node => (
|
||||
<Avatar.Icon
|
||||
size={size}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
icon={icon}
|
||||
color={color}
|
||||
/>
|
||||
)}
|
||||
right={(): React.Node => (
|
||||
<Avatar.Icon
|
||||
style={{
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
size={48}
|
||||
icon="chevron-right"
|
||||
/>
|
||||
)}
|
||||
style={{
|
||||
height,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(EquipmentListItem);
|
||||
|
|
|
@ -1,105 +1,102 @@
|
|||
// @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,
|
||||
route: {
|
||||
params?: {
|
||||
item?: Device,
|
||||
dates: [string, string]
|
||||
},
|
||||
type PropsType = {
|
||||
route: {
|
||||
params?: {
|
||||
item?: DeviceType,
|
||||
dates: [string, string],
|
||||
},
|
||||
theme: CustomTheme,
|
||||
}
|
||||
},
|
||||
theme: CustomTheme,
|
||||
};
|
||||
|
||||
class EquipmentConfirmScreen extends React.Component<PropsType> {
|
||||
item: DeviceType | null;
|
||||
|
||||
class EquipmentConfirmScreen extends React.Component<Props> {
|
||||
dates: [string, string] | null;
|
||||
|
||||
item: Device | null;
|
||||
dates: [string, string] | null;
|
||||
|
||||
constructor(props: Props) {
|
||||
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;
|
||||
}
|
||||
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() {
|
||||
const item = this.item;
|
||||
const dates = this.dates;
|
||||
if (item != null && dates != null) {
|
||||
const start = new Date(dates[0]);
|
||||
const end = new Date(dates[1]);
|
||||
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={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)
|
||||
})
|
||||
}
|
||||
</Button>
|
||||
<Paragraph style={{textAlign: "center"}}>
|
||||
{i18n.t("screens.equipment.bookingConfirmedMessage")}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</CollapsibleScrollView>
|
||||
);
|
||||
} else
|
||||
return 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);
|
||||
|
|
|
@ -1,193 +1,197 @@
|
|||
// @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 = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
type State = {
|
||||
mascotDialogVisible: boolean,
|
||||
}
|
||||
|
||||
export type Device = {
|
||||
id: number,
|
||||
name: string,
|
||||
caution: number,
|
||||
booked_at: Array<{ begin: string, end: string }>,
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
};
|
||||
|
||||
export type RentedDevice = {
|
||||
device_id: number,
|
||||
device_name: string,
|
||||
begin: string,
|
||||
end: string,
|
||||
}
|
||||
type StateType = {
|
||||
mascotDialogVisible: boolean,
|
||||
};
|
||||
|
||||
export type DeviceType = {
|
||||
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,
|
||||
};
|
||||
|
||||
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;
|
||||
canRefresh: boolean;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.canRefresh = false;
|
||||
this.authRef = React.createRef();
|
||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
||||
}
|
||||
|
||||
onScreenFocus = () => {
|
||||
if (this.canRefresh && this.authRef.current != null)
|
||||
this.authRef.current.reload();
|
||||
this.canRefresh = true;
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
|
||||
),
|
||||
};
|
||||
this.canRefresh = false;
|
||||
this.authRef = React.createRef();
|
||||
props.navigation.addListener('focus', this.onScreenFocus);
|
||||
}
|
||||
|
||||
getRenderItem = ({item}: { item: Device }) => {
|
||||
return (
|
||||
<EquipmentListItem
|
||||
navigation={this.props.navigation}
|
||||
item={item}
|
||||
userDeviceRentDates={this.getUserDeviceRentDates(item)}
|
||||
height={LIST_ITEM_HEIGHT}/>
|
||||
);
|
||||
};
|
||||
onScreenFocus = () => {
|
||||
if (this.canRefresh && this.authRef.current != null)
|
||||
this.authRef.current.reload();
|
||||
this.canRefresh = true;
|
||||
};
|
||||
|
||||
getUserDeviceRentDates(item: Device) {
|
||||
let dates = null;
|
||||
for (let i = 0; i < this.userRents.length; i++) {
|
||||
let device = this.userRents[i];
|
||||
if (item.id === device.device_id) {
|
||||
dates = [device.begin, device.end];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dates;
|
||||
getRenderItem = ({item}: {item: DeviceType}): React.Node => {
|
||||
const {navigation} = this.props;
|
||||
return (
|
||||
<EquipmentListItem
|
||||
navigation={navigation}
|
||||
item={item}
|
||||
userDeviceRentDates={this.getUserDeviceRentDates(item)}
|
||||
height={LIST_ITEM_HEIGHT}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
getUserDeviceRentDates(item: DeviceType): [number, number] | null {
|
||||
let dates = null;
|
||||
this.userRents.forEach((device: RentedDeviceType) => {
|
||||
if (item.id === device.device_id) {
|
||||
dates = [device.begin, device.end];
|
||||
}
|
||||
});
|
||||
return dates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list header, with explains this screen's purpose
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getListHeader(): React.Node {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: '100%',
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Button
|
||||
mode="contained"
|
||||
icon="help-circle"
|
||||
onPress={this.showMascotDialog}
|
||||
style={{
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
}}>
|
||||
{i18n.t('screens.equipment.mascotDialog.title')}
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
keyExtractor = (item: ClubType): string => item.id.toString();
|
||||
|
||||
/**
|
||||
* Gets the main screen component with the fetched data
|
||||
*
|
||||
* @param data The data fetched from the server
|
||||
* @returns {*}
|
||||
*/
|
||||
getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
|
||||
if (data[0] != null) {
|
||||
const fetchedData = data[0];
|
||||
if (fetchedData != null) this.data = fetchedData.devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list header, with explains this screen's purpose
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
getListHeader() {
|
||||
return (
|
||||
<View style={{
|
||||
width: "100%",
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Button
|
||||
mode={"contained"}
|
||||
icon={"help-circle"}
|
||||
onPress={this.showMascotDialog}
|
||||
style={{
|
||||
marginRight: "auto",
|
||||
marginLeft: "auto",
|
||||
}}>
|
||||
{i18n.t("screens.equipment.mascotDialog.title")}
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
if (data[1] != null) {
|
||||
const fetchedData = data[1];
|
||||
if (fetchedData != null) this.userRents = fetchedData.locations;
|
||||
}
|
||||
return (
|
||||
<CollapsibleFlatList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.getRenderItem}
|
||||
ListHeaderComponent={this.getListHeader()}
|
||||
data={this.data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
keyExtractor = (item: club) => item.id.toString();
|
||||
showMascotDialog = () => {
|
||||
this.setState({mascotDialogVisible: true});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the main screen component with the fetched data
|
||||
*
|
||||
* @param data The data fetched from the server
|
||||
* @returns {*}
|
||||
*/
|
||||
getScreen = (data: Array<{ [key: string]: any } | null>) => {
|
||||
if (data[0] != null) {
|
||||
const fetchedData = data[0];
|
||||
if (fetchedData != null)
|
||||
this.data = fetchedData["devices"];
|
||||
}
|
||||
if (data[1] != null) {
|
||||
const fetchedData = data[1];
|
||||
if (fetchedData != null)
|
||||
this.userRents = fetchedData["locations"];
|
||||
}
|
||||
return (
|
||||
<CollapsibleFlatList
|
||||
keyExtractor={this.keyExtractor}
|
||||
renderItem={this.getRenderItem}
|
||||
ListHeaderComponent={this.getListHeader()}
|
||||
data={this.data}
|
||||
/>
|
||||
)
|
||||
};
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.set(
|
||||
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
|
||||
false,
|
||||
);
|
||||
this.setState({mascotDialogVisible: false});
|
||||
};
|
||||
|
||||
showMascotDialog = () => {
|
||||
this.setState({mascotDialogVisible: true})
|
||||
};
|
||||
|
||||
hideMascotDialog = () => {
|
||||
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key, false);
|
||||
this.setState({mascotDialogVisible: false})
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<AuthenticatedScreen
|
||||
{...this.props}
|
||||
ref={this.authRef}
|
||||
requests={[
|
||||
{
|
||||
link: 'location/all',
|
||||
params: {},
|
||||
mandatory: true,
|
||||
},
|
||||
{
|
||||
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"}
|
||||
buttons={{
|
||||
action: null,
|
||||
cancel: {
|
||||
message: i18n.t("screens.equipment.mascotDialog.button"),
|
||||
icon: "check",
|
||||
onPress: this.hideMascotDialog,
|
||||
}
|
||||
}}
|
||||
emotion={MASCOT_STYLE.WINK}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
render(): React.Node {
|
||||
const {props, state} = this;
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<AuthenticatedScreen
|
||||
navigation={props.navigation}
|
||||
ref={this.authRef}
|
||||
requests={[
|
||||
{
|
||||
link: 'location/all',
|
||||
params: {},
|
||||
mandatory: true,
|
||||
},
|
||||
{
|
||||
link: 'location/my',
|
||||
params: {},
|
||||
mandatory: false,
|
||||
},
|
||||
]}
|
||||
renderFunction={this.getScreen}
|
||||
/>
|
||||
<MascotPopup
|
||||
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',
|
||||
onPress: this.hideMascotDialog,
|
||||
},
|
||||
}}
|
||||
emotion={MASCOT_STYLE.WINK}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(EquipmentListScreen);
|
||||
|
|
|
@ -1,441 +1,441 @@
|
|||
// @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 {
|
||||
generateMarkedDates,
|
||||
getFirstEquipmentAvailability,
|
||||
getISODate,
|
||||
getRelativeDateString,
|
||||
getValidRange,
|
||||
isEquipmentAvailable
|
||||
} from "../../../utils/EquipmentBooking";
|
||||
import ConnectionManager from "../../../managers/ConnectionManager";
|
||||
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
|
||||
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';
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
route: {
|
||||
params?: {
|
||||
item?: Device,
|
||||
},
|
||||
type PropsType = {
|
||||
navigation: StackNavigationProp,
|
||||
route: {
|
||||
params?: {
|
||||
item?: DeviceType,
|
||||
},
|
||||
theme: CustomTheme,
|
||||
}
|
||||
},
|
||||
theme: CustomTheme,
|
||||
};
|
||||
|
||||
type State = {
|
||||
dialogVisible: boolean,
|
||||
errorDialogVisible: boolean,
|
||||
markedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } },
|
||||
currentError: number,
|
||||
}
|
||||
export type MarkedDatesObjectType = {
|
||||
[key: string]: {startingDay: boolean, endingDay: boolean, color: string},
|
||||
};
|
||||
|
||||
class EquipmentRentScreen extends React.Component<Props, State> {
|
||||
type StateType = {
|
||||
dialogVisible: boolean,
|
||||
errorDialogVisible: boolean,
|
||||
markedDates: MarkedDatesObjectType,
|
||||
currentError: number,
|
||||
};
|
||||
|
||||
state = {
|
||||
dialogVisible: false,
|
||||
errorDialogVisible: false,
|
||||
markedDates: {},
|
||||
currentError: 0,
|
||||
}
|
||||
class EquipmentRentScreen extends React.Component<PropsType, StateType> {
|
||||
item: DeviceType | null;
|
||||
|
||||
item: Device | null;
|
||||
bookedDates: Array<string>;
|
||||
bookedDates: Array<string>;
|
||||
|
||||
bookRef: { current: null | Animatable.View }
|
||||
canBookEquipment: boolean;
|
||||
bookRef: {current: null | Animatable.View};
|
||||
|
||||
lockedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } }
|
||||
canBookEquipment: boolean;
|
||||
|
||||
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;
|
||||
}
|
||||
const item = this.item;
|
||||
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);
|
||||
this.lockedDates = {
|
||||
...this.lockedDates,
|
||||
...generateMarkedDates(
|
||||
false,
|
||||
this.props.theme,
|
||||
range
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
lockedDates: {
|
||||
[key: string]: {startingDay: boolean, endingDay: boolean, color: string},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures focus and blur events to hook on android back button
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.props.navigation.addListener(
|
||||
'focus',
|
||||
() =>
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid
|
||||
)
|
||||
);
|
||||
this.props.navigation.addListener(
|
||||
'blur',
|
||||
() =>
|
||||
BackHandler.removeEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides default android back button behaviour to deselect date if any is selected.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
onBackButtonPressAndroid = () => {
|
||||
if (this.bookedDates.length > 0) {
|
||||
this.resetSelection();
|
||||
this.updateMarkedSelection();
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this.state = {
|
||||
dialogVisible: false,
|
||||
errorDialogVisible: false,
|
||||
markedDates: {},
|
||||
currentError: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 }) => {
|
||||
const selected = new Date(day.dateString);
|
||||
const start = this.getBookStartDate();
|
||||
|
||||
if (!(this.lockedDates.hasOwnProperty(day.dateString))) {
|
||||
if (start === null) {
|
||||
this.updateSelectionRange(selected, selected);
|
||||
this.enableBooking();
|
||||
} else if (start.getTime() === selected.getTime()) {
|
||||
this.resetSelection();
|
||||
} else if (this.bookedDates.length === 1) {
|
||||
this.updateSelectionRange(start, selected);
|
||||
this.enableBooking();
|
||||
} else
|
||||
this.resetSelection();
|
||||
this.updateMarkedSelection();
|
||||
}
|
||||
this.resetSelection();
|
||||
this.bookRef = React.createRef();
|
||||
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;
|
||||
}
|
||||
|
||||
updateSelectionRange(start: Date, end: Date) {
|
||||
this.bookedDates = getValidRange(start, end, this.item);
|
||||
const {item} = this;
|
||||
if (item != null) {
|
||||
this.lockedDates = {};
|
||||
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, props.theme, range),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateMarkedSelection() {
|
||||
this.setState({
|
||||
markedDates: generateMarkedDates(
|
||||
true,
|
||||
this.props.theme,
|
||||
this.bookedDates
|
||||
),
|
||||
});
|
||||
/**
|
||||
* Captures focus and blur events to hook on android back button
|
||||
*/
|
||||
componentDidMount() {
|
||||
const {navigation} = this.props;
|
||||
navigation.addListener('focus', () => {
|
||||
BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid,
|
||||
);
|
||||
});
|
||||
navigation.addListener('blur', () => {
|
||||
BackHandler.removeEventListener(
|
||||
'hardwareBackPress',
|
||||
this.onBackButtonPressAndroid,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides default android back button behaviour to deselect date if any is selected.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
onBackButtonPressAndroid = (): boolean => {
|
||||
if (this.bookedDates.length > 0) {
|
||||
this.resetSelection();
|
||||
this.updateMarkedSelection();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
enableBooking() {
|
||||
if (!this.canBookEquipment) {
|
||||
this.showBookButton();
|
||||
this.canBookEquipment = true;
|
||||
}
|
||||
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,
|
||||
}) => {
|
||||
const selected = new Date(day.dateString);
|
||||
const start = this.getBookStartDate();
|
||||
|
||||
if (!this.lockedDates[day.dateString] != null) {
|
||||
if (start === null) {
|
||||
this.updateSelectionRange(selected, selected);
|
||||
this.enableBooking();
|
||||
} else if (start.getTime() === selected.getTime()) {
|
||||
this.resetSelection();
|
||||
} else if (this.bookedDates.length === 1) {
|
||||
this.updateSelectionRange(start, selected);
|
||||
this.enableBooking();
|
||||
} else this.resetSelection();
|
||||
this.updateMarkedSelection();
|
||||
}
|
||||
};
|
||||
|
||||
resetSelection() {
|
||||
if (this.canBookEquipment)
|
||||
this.hideBookButton();
|
||||
this.canBookEquipment = false;
|
||||
this.bookedDates = [];
|
||||
showErrorDialog = (error: number) => {
|
||||
this.setState({
|
||||
errorDialogVisible: true,
|
||||
currentError: error,
|
||||
});
|
||||
};
|
||||
|
||||
showDialog = () => {
|
||||
this.setState({dialogVisible: true});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the book button by plying a fade animation
|
||||
*/
|
||||
showBookButton() {
|
||||
if (this.bookRef.current != null) {
|
||||
this.bookRef.current.fadeInUp(500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the book button by plying a fade animation
|
||||
*/
|
||||
showBookButton() {
|
||||
if (this.bookRef.current != null) {
|
||||
this.bookRef.current.fadeInUp(500);
|
||||
}
|
||||
/**
|
||||
* Hides the book button by plying a fade animation
|
||||
*/
|
||||
hideBookButton() {
|
||||
if (this.bookRef.current != null) {
|
||||
this.bookRef.current.fadeOutDown(500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the book button by plying a fade animation
|
||||
*/
|
||||
hideBookButton() {
|
||||
if (this.bookRef.current != null) {
|
||||
this.bookRef.current.fadeOutDown(500);
|
||||
}
|
||||
enableBooking() {
|
||||
if (!this.canBookEquipment) {
|
||||
this.showBookButton();
|
||||
this.canBookEquipment = true;
|
||||
}
|
||||
}
|
||||
|
||||
showDialog = () => {
|
||||
this.setState({dialogVisible: true});
|
||||
}
|
||||
resetSelection() {
|
||||
if (this.canBookEquipment) this.hideBookButton();
|
||||
this.canBookEquipment = false;
|
||||
this.bookedDates = [];
|
||||
}
|
||||
|
||||
showErrorDialog = (error: number) => {
|
||||
this.setState({
|
||||
errorDialogVisible: true,
|
||||
currentError: error,
|
||||
});
|
||||
}
|
||||
updateSelectionRange(start: Date, end: Date) {
|
||||
this.bookedDates = getValidRange(start, end, this.item);
|
||||
}
|
||||
|
||||
onDialogDismiss = () => {
|
||||
this.setState({dialogVisible: false});
|
||||
}
|
||||
updateMarkedSelection() {
|
||||
const {theme} = this.props;
|
||||
this.setState({
|
||||
markedDates: generateMarkedDates(true, theme, this.bookedDates),
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
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)]
|
||||
});
|
||||
resolve();
|
||||
})
|
||||
.catch((error: number) => {
|
||||
this.onDialogDismiss();
|
||||
this.showErrorDialog(error);
|
||||
resolve();
|
||||
});
|
||||
} 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);
|
||||
return (
|
||||
render(): React.Node {
|
||||
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())
|
||||
subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
|
||||
begin: getRelativeDateString(start),
|
||||
end: getRelativeDateString(end),
|
||||
});
|
||||
else
|
||||
i18n.t('screens.equipment.bookingDay', {
|
||||
date: getRelativeDateString(start),
|
||||
});
|
||||
if (item != null) {
|
||||
const isAvailable = isEquipmentAvailable(item);
|
||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<CollapsibleScrollView>
|
||||
<Card style={{margin: 5}}>
|
||||
<Card.Content>
|
||||
<View style={{flex: 1}}>
|
||||
<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={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)
|
||||
})
|
||||
}
|
||||
|
||||
</Subheading>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
<CalendarList
|
||||
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
|
||||
minDate={new Date()}
|
||||
// Max amount of months allowed to scroll to the past. Default = 50
|
||||
pastScrollRange={0}
|
||||
// Max amount of months allowed to scroll to the future. Default = 50
|
||||
futureScrollRange={3}
|
||||
// Enable horizontal scrolling, default = false
|
||||
horizontal={true}
|
||||
// Enable paging on horizontal, default = false
|
||||
pagingEnabled={true}
|
||||
// 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}
|
||||
// Hide month navigation arrows.
|
||||
hideArrows={false}
|
||||
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
|
||||
markingType={'period'}
|
||||
markedDates={{...this.lockedDates, ...this.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,
|
||||
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,
|
||||
selectedDotColor: '#ffffff',
|
||||
arrowColor: this.props.theme.colors.primary,
|
||||
monthTextColor: this.props.theme.colors.text,
|
||||
indicatorColor: this.props.theme.colors.primary,
|
||||
textDayFontFamily: 'monospace',
|
||||
textMonthFontFamily: 'monospace',
|
||||
textDayHeaderFontFamily: 'monospace',
|
||||
textDayFontWeight: '300',
|
||||
textMonthFontWeight: 'bold',
|
||||
textDayHeaderFontWeight: '300',
|
||||
textDayFontSize: 16,
|
||||
textMonthFontSize: 16,
|
||||
textDayHeaderFontSize: 16,
|
||||
'stylesheet.day.period': {
|
||||
base: {
|
||||
overflow: 'hidden',
|
||||
height: 34,
|
||||
width: 34,
|
||||
alignItems: 'center',
|
||||
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{marginBottom: 50}}
|
||||
/>
|
||||
</CollapsibleScrollView>
|
||||
<LoadingConfirmDialog
|
||||
visible={this.state.dialogVisible}
|
||||
onDismiss={this.onDialogDismiss}
|
||||
onAccept={this.onDialogAccept}
|
||||
title={i18n.t('screens.equipment.dialogTitle')}
|
||||
titleLoading={i18n.t('screens.equipment.dialogTitleLoading')}
|
||||
message={i18n.t('screens.equipment.dialogMessage')}
|
||||
/>
|
||||
|
||||
<ErrorDialog
|
||||
visible={this.state.errorDialogVisible}
|
||||
onDismiss={this.onErrorDialogDismiss}
|
||||
errorCode={this.state.currentError}
|
||||
/>
|
||||
<Animatable.View
|
||||
ref={this.bookRef}
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
transform: [
|
||||
{translateY: 100},
|
||||
]
|
||||
}}>
|
||||
<Button
|
||||
icon="bookmark-check"
|
||||
mode="contained"
|
||||
onPress={this.showDialog}
|
||||
style={{
|
||||
width: "80%",
|
||||
flex: 1,
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginBottom: 20,
|
||||
borderRadius: 10
|
||||
}}
|
||||
>
|
||||
{i18n.t('screens.equipment.bookButton')}
|
||||
</Button>
|
||||
</Animatable.View>
|
||||
|
||||
<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>
|
||||
|
||||
)
|
||||
} else
|
||||
return <View/>;
|
||||
}
|
||||
<Button
|
||||
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>
|
||||
<CalendarList
|
||||
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
|
||||
minDate={new Date()}
|
||||
// Max amount of months allowed to scroll to the past. Default = 50
|
||||
pastScrollRange={0}
|
||||
// Max amount of months allowed to scroll to the future. Default = 50
|
||||
futureScrollRange={3}
|
||||
// Enable horizontal scrolling, default = false
|
||||
horizontal
|
||||
// Enable paging on horizontal, default = false
|
||||
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
|
||||
// Hide month navigation arrows.
|
||||
hideArrows={false}
|
||||
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
|
||||
markingType="period"
|
||||
markedDates={{...this.lockedDates, ...state.markedDates}}
|
||||
theme={{
|
||||
backgroundColor: props.theme.colors.agendaBackgroundColor,
|
||||
calendarBackground: props.theme.colors.background,
|
||||
textSectionTitleColor: props.theme.colors.agendaDayTextColor,
|
||||
selectedDayBackgroundColor: props.theme.colors.primary,
|
||||
selectedDayTextColor: '#ffffff',
|
||||
todayTextColor: props.theme.colors.text,
|
||||
dayTextColor: props.theme.colors.text,
|
||||
textDisabledColor: props.theme.colors.agendaDayTextColor,
|
||||
dotColor: props.theme.colors.primary,
|
||||
selectedDotColor: '#ffffff',
|
||||
arrowColor: props.theme.colors.primary,
|
||||
monthTextColor: props.theme.colors.text,
|
||||
indicatorColor: props.theme.colors.primary,
|
||||
textDayFontFamily: 'monospace',
|
||||
textMonthFontFamily: 'monospace',
|
||||
textDayHeaderFontFamily: 'monospace',
|
||||
textDayFontWeight: '300',
|
||||
textMonthFontWeight: 'bold',
|
||||
textDayHeaderFontWeight: '300',
|
||||
textDayFontSize: 16,
|
||||
textMonthFontSize: 16,
|
||||
textDayHeaderFontSize: 16,
|
||||
'stylesheet.day.period': {
|
||||
base: {
|
||||
overflow: 'hidden',
|
||||
height: 34,
|
||||
width: 34,
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
}}
|
||||
style={{marginBottom: 50}}
|
||||
/>
|
||||
</CollapsibleScrollView>
|
||||
<LoadingConfirmDialog
|
||||
visible={state.dialogVisible}
|
||||
onDismiss={this.onDialogDismiss}
|
||||
onAccept={this.onDialogAccept}
|
||||
title={i18n.t('screens.equipment.dialogTitle')}
|
||||
titleLoading={i18n.t('screens.equipment.dialogTitleLoading')}
|
||||
message={i18n.t('screens.equipment.dialogMessage')}
|
||||
/>
|
||||
|
||||
<ErrorDialog
|
||||
visible={state.errorDialogVisible}
|
||||
onDismiss={this.onErrorDialogDismiss}
|
||||
errorCode={state.currentError}
|
||||
/>
|
||||
<Animatable.View
|
||||
ref={this.bookRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
flex: 1,
|
||||
transform: [{translateY: 100}],
|
||||
}}>
|
||||
<Button
|
||||
icon="bookmark-check"
|
||||
mode="contained"
|
||||
onPress={this.showDialog}
|
||||
style={{
|
||||
width: '80%',
|
||||
flex: 1,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginBottom: 20,
|
||||
borderRadius: 10,
|
||||
}}>
|
||||
{i18n.t('screens.equipment.bookButton')}
|
||||
</Button>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(EquipmentRentScreen);
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
// @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());
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
return today;
|
||||
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,18 +33,16 @@ export function getISODate(date: Date) {
|
|||
* @param item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEquipmentAvailable(item: Device) {
|
||||
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;
|
||||
}
|
||||
return isAvailable;
|
||||
export function isEquipmentAvailable(item: DeviceType): boolean {
|
||||
let isAvailable = true;
|
||||
const today = getCurrentDay();
|
||||
const dates = item.booked_at;
|
||||
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,17 +51,16 @@ export function isEquipmentAvailable(item: Device) {
|
|||
* @param item
|
||||
* @returns {Date}
|
||||
*/
|
||||
export function getFirstEquipmentAvailability(item: Device) {
|
||||
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);
|
||||
end.setDate(end.getDate() + 1);
|
||||
if (firstAvailability >= start)
|
||||
firstAvailability = end;
|
||||
}
|
||||
return firstAvailability;
|
||||
export function getFirstEquipmentAvailability(item: DeviceType): Date {
|
||||
let firstAvailability = getCurrentDay();
|
||||
const dates = item.booked_at;
|
||||
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;
|
||||
});
|
||||
return firstAvailability;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,31 +68,31 @@ export function getFirstEquipmentAvailability(item: Device) {
|
|||
*
|
||||
* @param date The date to translate
|
||||
*/
|
||||
export function getRelativeDateString(date: Date) {
|
||||
const today = getCurrentDay();
|
||||
const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
|
||||
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
|
||||
const dayDelta = date.getUTCDate() - today.getUTCDate();
|
||||
let translatedString = i18n.t('screens.equipment.today');
|
||||
if (yearDelta > 0)
|
||||
translatedString = i18n.t('screens.equipment.otherYear', {
|
||||
date: date.getDate(),
|
||||
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
||||
year: date.getFullYear()
|
||||
});
|
||||
else if (monthDelta > 0)
|
||||
translatedString = i18n.t('screens.equipment.otherMonth', {
|
||||
date: date.getDate(),
|
||||
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
||||
});
|
||||
else if (dayDelta > 1)
|
||||
translatedString = i18n.t('screens.equipment.thisMonth', {
|
||||
date: date.getDate(),
|
||||
});
|
||||
else if (dayDelta === 1)
|
||||
translatedString = i18n.t('screens.equipment.tomorrow');
|
||||
export function getRelativeDateString(date: Date): string {
|
||||
const today = getCurrentDay();
|
||||
const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
|
||||
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
|
||||
const dayDelta = date.getUTCDate() - today.getUTCDate();
|
||||
let translatedString = i18n.t('screens.equipment.today');
|
||||
if (yearDelta > 0)
|
||||
translatedString = i18n.t('screens.equipment.otherYear', {
|
||||
date: date.getDate(),
|
||||
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
||||
year: date.getFullYear(),
|
||||
});
|
||||
else if (monthDelta > 0)
|
||||
translatedString = i18n.t('screens.equipment.otherMonth', {
|
||||
date: date.getDate(),
|
||||
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
||||
});
|
||||
else if (dayDelta > 1)
|
||||
translatedString = i18n.t('screens.equipment.thisMonth', {
|
||||
date: date.getDate(),
|
||||
});
|
||||
else if (dayDelta === 1)
|
||||
translatedString = i18n.t('screens.equipment.tomorrow');
|
||||
|
||||
return translatedString;
|
||||
return translatedString;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,41 +109,45 @@ 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;
|
||||
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++) {
|
||||
const bookLimit = new Date(item.booked_at[i].begin);
|
||||
if (start < bookLimit && limit > bookLimit) {
|
||||
limit = bookLimit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = item.booked_at.length - 1; i >= 0; i--) {
|
||||
const bookLimit = new Date(item.booked_at[i].end);
|
||||
if (start > bookLimit && limit < bookLimit) {
|
||||
limit = bookLimit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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 += 1) {
|
||||
const bookLimit = new Date(item.booked_at[i].begin);
|
||||
if (start < bookLimit && limit > bookLimit) {
|
||||
limit = bookLimit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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));
|
||||
date.setDate(date.getDate() + direction);
|
||||
}
|
||||
return validRange;
|
||||
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,20 +159,24 @@ 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++) {
|
||||
const isStart = i === 0;
|
||||
const isEnd = i === range.length - 1;
|
||||
markedDates[range[i]] = {
|
||||
startingDay: isStart,
|
||||
endingDay: isEnd,
|
||||
color: isSelection
|
||||
? isStart || isEnd
|
||||
? theme.colors.primary
|
||||
: theme.colors.danger
|
||||
: theme.colors.textDisabled
|
||||
};
|
||||
}
|
||||
return markedDates;
|
||||
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,
|
||||
};
|
||||
}
|
||||
return markedDates;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue