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,46 +2,48 @@
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Avatar, List, withTheme} from 'react-native-paper';
|
import {Avatar, List, withTheme} from 'react-native-paper';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import i18n from 'i18n-js';
|
||||||
import type {Device} from "../../../screens/Amicale/Equipment/EquipmentListScreen";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import i18n from "i18n-js";
|
import type {CustomTheme} from '../../../managers/ThemeManager';
|
||||||
|
import type {DeviceType} from '../../../screens/Amicale/Equipment/EquipmentListScreen';
|
||||||
import {
|
import {
|
||||||
getFirstEquipmentAvailability,
|
getFirstEquipmentAvailability,
|
||||||
getRelativeDateString,
|
getRelativeDateString,
|
||||||
isEquipmentAvailable
|
isEquipmentAvailable,
|
||||||
} from "../../../utils/EquipmentBooking";
|
} from '../../../utils/EquipmentBooking';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
userDeviceRentDates: [string, string],
|
userDeviceRentDates: [string, string],
|
||||||
item: Device,
|
item: DeviceType,
|
||||||
height: number,
|
height: number,
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
class EquipmentListItem extends React.Component<PropsType> {
|
||||||
|
shouldComponentUpdate(nextProps: PropsType): boolean {
|
||||||
|
const {userDeviceRentDates} = this.props;
|
||||||
|
return nextProps.userDeviceRentDates !== userDeviceRentDates;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EquipmentListItem extends React.Component<Props> {
|
render(): React.Node {
|
||||||
|
const {item, userDeviceRentDates, navigation, height, theme} = this.props;
|
||||||
shouldComponentUpdate(nextProps: Props): boolean {
|
|
||||||
return nextProps.userDeviceRentDates !== this.props.userDeviceRentDates;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const colors = this.props.theme.colors;
|
|
||||||
const item = this.props.item;
|
|
||||||
const userDeviceRentDates = this.props.userDeviceRentDates;
|
|
||||||
const isRented = userDeviceRentDates != null;
|
const isRented = userDeviceRentDates != null;
|
||||||
const isAvailable = isEquipmentAvailable(item);
|
const isAvailable = isEquipmentAvailable(item);
|
||||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||||
|
|
||||||
let onPress;
|
let onPress;
|
||||||
if (isRented)
|
if (isRented)
|
||||||
onPress = () => this.props.navigation.navigate("equipment-confirm", {
|
onPress = () => {
|
||||||
item: item,
|
navigation.navigate('equipment-confirm', {
|
||||||
dates: userDeviceRentDates
|
item,
|
||||||
|
dates: userDeviceRentDates,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
else
|
else
|
||||||
onPress = () => this.props.navigation.navigate("equipment-rent", {item: item});
|
onPress = () => {
|
||||||
|
navigation.navigate('equipment-rent', {item});
|
||||||
|
};
|
||||||
|
|
||||||
let description;
|
let description;
|
||||||
if (isRented) {
|
if (isRented) {
|
||||||
|
@ -50,58 +52,57 @@ class EquipmentListItem extends React.Component<Props> {
|
||||||
if (start.getTime() !== end.getTime())
|
if (start.getTime() !== end.getTime())
|
||||||
description = i18n.t('screens.equipment.bookingPeriod', {
|
description = i18n.t('screens.equipment.bookingPeriod', {
|
||||||
begin: getRelativeDateString(start),
|
begin: getRelativeDateString(start),
|
||||||
end: getRelativeDateString(end)
|
end: getRelativeDateString(end),
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
description = i18n.t('screens.equipment.bookingDay', {
|
description = i18n.t('screens.equipment.bookingDay', {
|
||||||
date: getRelativeDateString(start)
|
date: getRelativeDateString(start),
|
||||||
});
|
});
|
||||||
} else if (isAvailable)
|
} else if (isAvailable)
|
||||||
description = i18n.t('screens.equipment.bail', {cost: item.caution});
|
description = i18n.t('screens.equipment.bail', {cost: item.caution});
|
||||||
else
|
else
|
||||||
description = i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)});
|
description = i18n.t('screens.equipment.available', {
|
||||||
|
date: getRelativeDateString(firstAvailability),
|
||||||
|
});
|
||||||
|
|
||||||
let icon;
|
let icon;
|
||||||
if (isRented)
|
if (isRented) icon = 'bookmark-check';
|
||||||
icon = "bookmark-check";
|
else if (isAvailable) icon = 'check-circle-outline';
|
||||||
else if (isAvailable)
|
else icon = 'update';
|
||||||
icon = "check-circle-outline";
|
|
||||||
else
|
|
||||||
icon = "update";
|
|
||||||
|
|
||||||
let color;
|
let color;
|
||||||
if (isRented)
|
if (isRented) color = theme.colors.warning;
|
||||||
color = colors.warning;
|
else if (isAvailable) color = theme.colors.success;
|
||||||
else if (isAvailable)
|
else color = theme.colors.primary;
|
||||||
color = colors.success;
|
|
||||||
else
|
|
||||||
color = colors.primary;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
title={item.name}
|
title={item.name}
|
||||||
description={description}
|
description={description}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
left={(props) => <Avatar.Icon
|
left={({size}: {size: number}): React.Node => (
|
||||||
{...props}
|
<Avatar.Icon
|
||||||
|
size={size}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
color={color}
|
color={color}
|
||||||
/>}
|
/>
|
||||||
right={(props) => <Avatar.Icon
|
)}
|
||||||
{...props}
|
right={(): React.Node => (
|
||||||
|
<Avatar.Icon
|
||||||
style={{
|
style={{
|
||||||
marginTop: 'auto',
|
marginTop: 'auto',
|
||||||
marginBottom: 'auto',
|
marginBottom: 'auto',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
size={48}
|
size={48}
|
||||||
icon={"chevron-right"}
|
icon="chevron-right"
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
height: this.props.height,
|
height,
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,68 +1,79 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Button, Caption, Card, Headline, Paragraph, withTheme} from 'react-native-paper';
|
import {
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
Button,
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
Caption,
|
||||||
import type {Device} from "./EquipmentListScreen";
|
Card,
|
||||||
import {View} from "react-native";
|
Headline,
|
||||||
import i18n from "i18n-js";
|
Paragraph,
|
||||||
import {getRelativeDateString} from "../../../utils/EquipmentBooking";
|
withTheme,
|
||||||
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
|
} 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 = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
|
||||||
route: {
|
route: {
|
||||||
params?: {
|
params?: {
|
||||||
item?: Device,
|
item?: DeviceType,
|
||||||
dates: [string, string]
|
dates: [string, string],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
class EquipmentConfirmScreen extends React.Component<PropsType> {
|
||||||
|
item: DeviceType | null;
|
||||||
|
|
||||||
class EquipmentConfirmScreen extends React.Component<Props> {
|
|
||||||
|
|
||||||
item: Device | null;
|
|
||||||
dates: [string, string] | null;
|
dates: [string, string] | null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
if (this.props.route.params != null) {
|
if (props.route.params != null) {
|
||||||
if (this.props.route.params.item != null)
|
if (props.route.params.item != null) this.item = props.route.params.item;
|
||||||
this.item = this.props.route.params.item;
|
else this.item = null;
|
||||||
else
|
if (props.route.params.dates != null)
|
||||||
this.item = null;
|
this.dates = props.route.params.dates;
|
||||||
if (this.props.route.params.dates != null)
|
else this.dates = null;
|
||||||
this.dates = this.props.route.params.dates;
|
|
||||||
else
|
|
||||||
this.dates = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
const item = this.item;
|
const {item, dates, props} = this;
|
||||||
const dates = this.dates;
|
|
||||||
if (item != null && dates != null) {
|
if (item != null && dates != null) {
|
||||||
const start = new Date(dates[0]);
|
const start = new Date(dates[0]);
|
||||||
const end = new Date(dates[1]);
|
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 (
|
return (
|
||||||
<CollapsibleScrollView>
|
<CollapsibleScrollView>
|
||||||
<Card style={{margin: 5}}>
|
<Card style={{margin: 5}}>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<View style={{
|
<View
|
||||||
marginLeft: "auto",
|
style={{
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
flexDirection: "row",
|
marginRight: 'auto',
|
||||||
flexWrap: "wrap",
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
<Headline style={{textAlign: "center"}}>
|
<Headline style={{textAlign: 'center'}}>{item.name}</Headline>
|
||||||
{item.name}
|
<Caption
|
||||||
</Headline>
|
style={{
|
||||||
<Caption style={{
|
textAlign: 'center',
|
||||||
textAlign: "center",
|
|
||||||
lineHeight: 35,
|
lineHeight: 35,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
}}>
|
}}>
|
||||||
|
@ -71,35 +82,21 @@ class EquipmentConfirmScreen extends React.Component<Props> {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
icon={"check-circle-outline"}
|
icon="check-circle-outline"
|
||||||
color={this.props.theme.colors.success}
|
color={props.theme.colors.success}
|
||||||
mode="text"
|
mode="text">
|
||||||
>
|
{buttonText}
|
||||||
{
|
|
||||||
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>
|
</Button>
|
||||||
<Paragraph style={{textAlign: "center"}}>
|
<Paragraph style={{textAlign: 'center'}}>
|
||||||
{i18n.t("screens.equipment.bookingConfirmedMessage")}
|
{i18n.t('screens.equipment.bookingConfirmedMessage')}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
);
|
);
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(EquipmentConfirmScreen);
|
export default withTheme(EquipmentConfirmScreen);
|
||||||
|
|
|
@ -1,61 +1,62 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from "react-native";
|
import {View} from 'react-native';
|
||||||
import {Button, withTheme} from 'react-native-paper';
|
import {Button, withTheme} from 'react-native-paper';
|
||||||
import AuthenticatedScreen from "../../../components/Amicale/AuthenticatedScreen";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import i18n from 'i18n-js';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import AuthenticatedScreen from '../../../components/Amicale/AuthenticatedScreen';
|
||||||
import i18n from "i18n-js";
|
import type {ClubType} from '../Clubs/ClubListScreen';
|
||||||
import type {club} from "../Clubs/ClubListScreen";
|
import EquipmentListItem from '../../../components/Lists/Equipment/EquipmentListItem';
|
||||||
import EquipmentListItem from "../../../components/Lists/Equipment/EquipmentListItem";
|
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||||
import MascotPopup from "../../../components/Mascot/MascotPopup";
|
import {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
|
||||||
import {MASCOT_STYLE} from "../../../components/Mascot/Mascot";
|
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||||
import AsyncStorageManager from "../../../managers/AsyncStorageManager";
|
import CollapsibleFlatList from '../../../components/Collapsible/CollapsibleFlatList';
|
||||||
import CollapsibleFlatList from "../../../components/Collapsible/CollapsibleFlatList";
|
import type {ApiGenericDataType} from '../../../utils/WebData';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
theme: CustomTheme,
|
};
|
||||||
}
|
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
mascotDialogVisible: boolean,
|
mascotDialogVisible: boolean,
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Device = {
|
export type DeviceType = {
|
||||||
id: number,
|
id: number,
|
||||||
name: string,
|
name: string,
|
||||||
caution: number,
|
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_id: number,
|
||||||
device_name: string,
|
device_name: string,
|
||||||
begin: string,
|
begin: string,
|
||||||
end: string,
|
end: string,
|
||||||
}
|
};
|
||||||
|
|
||||||
const LIST_ITEM_HEIGHT = 64;
|
const LIST_ITEM_HEIGHT = 64;
|
||||||
|
|
||||||
class EquipmentListScreen extends React.Component<Props, State> {
|
class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
|
data: Array<DeviceType>;
|
||||||
|
|
||||||
state = {
|
userRents: Array<RentedDeviceType>;
|
||||||
mascotDialogVisible: AsyncStorageManager.getBool(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key),
|
|
||||||
}
|
|
||||||
|
|
||||||
data: Array<Device>;
|
|
||||||
userRents: Array<RentedDevice>;
|
|
||||||
|
|
||||||
authRef: {current: null | AuthenticatedScreen};
|
authRef: {current: null | AuthenticatedScreen};
|
||||||
|
|
||||||
canRefresh: boolean;
|
canRefresh: boolean;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
mascotDialogVisible: AsyncStorageManager.getBool(
|
||||||
|
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
|
||||||
|
),
|
||||||
|
};
|
||||||
this.canRefresh = false;
|
this.canRefresh = false;
|
||||||
this.authRef = React.createRef();
|
this.authRef = React.createRef();
|
||||||
this.props.navigation.addListener('focus', this.onScreenFocus);
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
onScreenFocus = () => {
|
onScreenFocus = () => {
|
||||||
|
@ -64,25 +65,25 @@ class EquipmentListScreen extends React.Component<Props, State> {
|
||||||
this.canRefresh = true;
|
this.canRefresh = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
getRenderItem = ({item}: { item: Device }) => {
|
getRenderItem = ({item}: {item: DeviceType}): React.Node => {
|
||||||
|
const {navigation} = this.props;
|
||||||
return (
|
return (
|
||||||
<EquipmentListItem
|
<EquipmentListItem
|
||||||
navigation={this.props.navigation}
|
navigation={navigation}
|
||||||
item={item}
|
item={item}
|
||||||
userDeviceRentDates={this.getUserDeviceRentDates(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;
|
let dates = null;
|
||||||
for (let i = 0; i < this.userRents.length; i++) {
|
this.userRents.forEach((device: RentedDeviceType) => {
|
||||||
let device = this.userRents[i];
|
|
||||||
if (item.id === device.device_id) {
|
if (item.id === device.device_id) {
|
||||||
dates = [device.begin, device.end];
|
dates = [device.begin, device.end];
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return dates;
|
return dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,28 +92,29 @@ class EquipmentListScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getListHeader() {
|
getListHeader(): React.Node {
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
width: "100%",
|
style={{
|
||||||
|
width: '100%',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
mode={"contained"}
|
mode="contained"
|
||||||
icon={"help-circle"}
|
icon="help-circle"
|
||||||
onPress={this.showMascotDialog}
|
onPress={this.showMascotDialog}
|
||||||
style={{
|
style={{
|
||||||
marginRight: "auto",
|
marginRight: 'auto',
|
||||||
marginLeft: "auto",
|
marginLeft: 'auto',
|
||||||
}}>
|
}}>
|
||||||
{i18n.t("screens.equipment.mascotDialog.title")}
|
{i18n.t('screens.equipment.mascotDialog.title')}
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyExtractor = (item: club) => item.id.toString();
|
keyExtractor = (item: ClubType): string => item.id.toString();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the main screen component with the fetched data
|
* 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
|
* @param data The data fetched from the server
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
getScreen = (data: Array<{ [key: string]: any } | null>) => {
|
getScreen = (data: Array<ApiGenericDataType | null>): React.Node => {
|
||||||
if (data[0] != null) {
|
if (data[0] != null) {
|
||||||
const fetchedData = data[0];
|
const fetchedData = data[0];
|
||||||
if (fetchedData != null)
|
if (fetchedData != null) this.data = fetchedData.devices;
|
||||||
this.data = fetchedData["devices"];
|
|
||||||
}
|
}
|
||||||
if (data[1] != null) {
|
if (data[1] != null) {
|
||||||
const fetchedData = data[1];
|
const fetchedData = data[1];
|
||||||
if (fetchedData != null)
|
if (fetchedData != null) this.userRents = fetchedData.locations;
|
||||||
this.userRents = fetchedData["locations"];
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<CollapsibleFlatList
|
<CollapsibleFlatList
|
||||||
|
@ -138,23 +138,27 @@ class EquipmentListScreen extends React.Component<Props, State> {
|
||||||
ListHeaderComponent={this.getListHeader()}
|
ListHeaderComponent={this.getListHeader()}
|
||||||
data={this.data}
|
data={this.data}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
showMascotDialog = () => {
|
showMascotDialog = () => {
|
||||||
this.setState({mascotDialogVisible: true})
|
this.setState({mascotDialogVisible: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
hideMascotDialog = () => {
|
hideMascotDialog = () => {
|
||||||
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.equipmentShowBanner.key, false);
|
AsyncStorageManager.set(
|
||||||
this.setState({mascotDialogVisible: false})
|
AsyncStorageManager.PREFERENCES.equipmentShowBanner.key,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this.setState({mascotDialogVisible: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<AuthenticatedScreen
|
<AuthenticatedScreen
|
||||||
{...this.props}
|
navigation={props.navigation}
|
||||||
ref={this.authRef}
|
ref={this.authRef}
|
||||||
requests={[
|
requests={[
|
||||||
{
|
{
|
||||||
|
@ -166,22 +170,22 @@ class EquipmentListScreen extends React.Component<Props, State> {
|
||||||
link: 'location/my',
|
link: 'location/my',
|
||||||
params: {},
|
params: {},
|
||||||
mandatory: false,
|
mandatory: false,
|
||||||
}
|
},
|
||||||
]}
|
]}
|
||||||
renderFunction={this.getScreen}
|
renderFunction={this.getScreen}
|
||||||
/>
|
/>
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
visible={this.state.mascotDialogVisible}
|
visible={state.mascotDialogVisible}
|
||||||
title={i18n.t("screens.equipment.mascotDialog.title")}
|
title={i18n.t('screens.equipment.mascotDialog.title')}
|
||||||
message={i18n.t("screens.equipment.mascotDialog.message")}
|
message={i18n.t('screens.equipment.mascotDialog.message')}
|
||||||
icon={"vote"}
|
icon="vote"
|
||||||
buttons={{
|
buttons={{
|
||||||
action: null,
|
action: null,
|
||||||
cancel: {
|
cancel: {
|
||||||
message: i18n.t("screens.equipment.mascotDialog.button"),
|
message: i18n.t('screens.equipment.mascotDialog.button'),
|
||||||
icon: "check",
|
icon: 'check',
|
||||||
onPress: this.hideMascotDialog,
|
onPress: this.hideMascotDialog,
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
emotion={MASCOT_STYLE.WINK}
|
emotion={MASCOT_STYLE.WINK}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,111 +1,118 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Button, Caption, Card, Headline, Subheading, withTheme} from 'react-native-paper';
|
import {
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
Button,
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
Caption,
|
||||||
import type {Device} from "./EquipmentListScreen";
|
Card,
|
||||||
import {BackHandler, View} from "react-native";
|
Headline,
|
||||||
import * as Animatable from "react-native-animatable";
|
Subheading,
|
||||||
import i18n from "i18n-js";
|
withTheme,
|
||||||
import {CalendarList} from "react-native-calendars";
|
} from 'react-native-paper';
|
||||||
import LoadingConfirmDialog from "../../../components/Dialogs/LoadingConfirmDialog";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import ErrorDialog from "../../../components/Dialogs/ErrorDialog";
|
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 {
|
import {
|
||||||
generateMarkedDates,
|
generateMarkedDates,
|
||||||
getFirstEquipmentAvailability,
|
getFirstEquipmentAvailability,
|
||||||
getISODate,
|
getISODate,
|
||||||
getRelativeDateString,
|
getRelativeDateString,
|
||||||
getValidRange,
|
getValidRange,
|
||||||
isEquipmentAvailable
|
isEquipmentAvailable,
|
||||||
} from "../../../utils/EquipmentBooking";
|
} from '../../../utils/EquipmentBooking';
|
||||||
import ConnectionManager from "../../../managers/ConnectionManager";
|
import ConnectionManager from '../../../managers/ConnectionManager';
|
||||||
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: {
|
route: {
|
||||||
params?: {
|
params?: {
|
||||||
item?: Device,
|
item?: DeviceType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
theme: CustomTheme,
|
theme: CustomTheme,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
export type MarkedDatesObjectType = {
|
||||||
|
[key: string]: {startingDay: boolean, endingDay: boolean, color: string},
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
errorDialogVisible: boolean,
|
errorDialogVisible: boolean,
|
||||||
markedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } },
|
markedDates: MarkedDatesObjectType,
|
||||||
currentError: number,
|
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,
|
dialogVisible: false,
|
||||||
errorDialogVisible: false,
|
errorDialogVisible: false,
|
||||||
markedDates: {},
|
markedDates: {},
|
||||||
currentError: 0,
|
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.resetSelection();
|
||||||
this.bookRef = React.createRef();
|
this.bookRef = React.createRef();
|
||||||
this.canBookEquipment = false;
|
this.canBookEquipment = false;
|
||||||
this.bookedDates = [];
|
this.bookedDates = [];
|
||||||
if (this.props.route.params != null) {
|
if (props.route.params != null) {
|
||||||
if (this.props.route.params.item != null)
|
if (props.route.params.item != null) this.item = props.route.params.item;
|
||||||
this.item = this.props.route.params.item;
|
else this.item = null;
|
||||||
else
|
|
||||||
this.item = null;
|
|
||||||
}
|
}
|
||||||
const item = this.item;
|
const {item} = this;
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
this.lockedDates = {};
|
this.lockedDates = {};
|
||||||
for (let i = 0; i < item.booked_at.length; i++) {
|
item.booked_at.forEach((date: {begin: string, end: string}) => {
|
||||||
const range = getValidRange(new Date(item.booked_at[i].begin), new Date(item.booked_at[i].end), null);
|
const range = getValidRange(
|
||||||
|
new Date(date.begin),
|
||||||
|
new Date(date.end),
|
||||||
|
null,
|
||||||
|
);
|
||||||
this.lockedDates = {
|
this.lockedDates = {
|
||||||
...this.lockedDates,
|
...this.lockedDates,
|
||||||
...generateMarkedDates(
|
...generateMarkedDates(false, props.theme, range),
|
||||||
false,
|
|
||||||
this.props.theme,
|
|
||||||
range
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Captures focus and blur events to hook on android back button
|
* Captures focus and blur events to hook on android back button
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.navigation.addListener(
|
const {navigation} = this.props;
|
||||||
'focus',
|
navigation.addListener('focus', () => {
|
||||||
() =>
|
|
||||||
BackHandler.addEventListener(
|
BackHandler.addEventListener(
|
||||||
'hardwareBackPress',
|
'hardwareBackPress',
|
||||||
this.onBackButtonPressAndroid
|
this.onBackButtonPressAndroid,
|
||||||
)
|
|
||||||
);
|
);
|
||||||
this.props.navigation.addListener(
|
});
|
||||||
'blur',
|
navigation.addListener('blur', () => {
|
||||||
() =>
|
|
||||||
BackHandler.removeEventListener(
|
BackHandler.removeEventListener(
|
||||||
'hardwareBackPress',
|
'hardwareBackPress',
|
||||||
this.onBackButtonPressAndroid
|
this.onBackButtonPressAndroid,
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,26 +120,88 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
onBackButtonPressAndroid = () => {
|
onBackButtonPressAndroid = (): boolean => {
|
||||||
if (this.bookedDates.length > 0) {
|
if (this.bookedDates.length > 0) {
|
||||||
this.resetSelection();
|
this.resetSelection();
|
||||||
this.updateMarkedSelection();
|
this.updateMarkedSelection();
|
||||||
return true;
|
return true;
|
||||||
} else
|
}
|
||||||
return false;
|
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.
|
* Selects a new date on the calendar.
|
||||||
* If both start and end dates are already selected, unselect all.
|
* If both start and end dates are already selected, unselect all.
|
||||||
*
|
*
|
||||||
* @param day The day selected
|
* @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 selected = new Date(day.dateString);
|
||||||
const start = this.getBookStartDate();
|
const start = this.getBookStartDate();
|
||||||
|
|
||||||
if (!(this.lockedDates.hasOwnProperty(day.dateString))) {
|
if (!this.lockedDates[day.dateString] != null) {
|
||||||
if (start === null) {
|
if (start === null) {
|
||||||
this.updateSelectionRange(selected, selected);
|
this.updateSelectionRange(selected, selected);
|
||||||
this.enableBooking();
|
this.enableBooking();
|
||||||
|
@ -141,39 +210,21 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
} else if (this.bookedDates.length === 1) {
|
} else if (this.bookedDates.length === 1) {
|
||||||
this.updateSelectionRange(start, selected);
|
this.updateSelectionRange(start, selected);
|
||||||
this.enableBooking();
|
this.enableBooking();
|
||||||
} else
|
} else this.resetSelection();
|
||||||
this.resetSelection();
|
|
||||||
this.updateMarkedSelection();
|
this.updateMarkedSelection();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
updateSelectionRange(start: Date, end: Date) {
|
showErrorDialog = (error: number) => {
|
||||||
this.bookedDates = getValidRange(start, end, this.item);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMarkedSelection() {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
markedDates: generateMarkedDates(
|
errorDialogVisible: true,
|
||||||
true,
|
currentError: error,
|
||||||
this.props.theme,
|
|
||||||
this.bookedDates
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
enableBooking() {
|
showDialog = () => {
|
||||||
if (!this.canBookEquipment) {
|
this.setState({dialogVisible: true});
|
||||||
this.showBookButton();
|
};
|
||||||
this.canBookEquipment = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetSelection() {
|
|
||||||
if (this.canBookEquipment)
|
|
||||||
this.hideBookButton();
|
|
||||||
this.canBookEquipment = false;
|
|
||||||
this.bookedDates = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the book button by plying a fade animation
|
* Shows the book button by plying a fade animation
|
||||||
|
@ -193,84 +244,45 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showDialog = () => {
|
enableBooking() {
|
||||||
this.setState({dialogVisible: true});
|
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({
|
this.setState({
|
||||||
errorDialogVisible: true,
|
markedDates: generateMarkedDates(true, theme, this.bookedDates),
|
||||||
currentError: error,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onDialogDismiss = () => {
|
render(): React.Node {
|
||||||
this.setState({dialogVisible: false});
|
const {item, props, state} = this;
|
||||||
}
|
|
||||||
|
|
||||||
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 start = this.getBookStartDate();
|
||||||
const end = this.getBookEndDate();
|
const end = this.getBookEndDate();
|
||||||
if (item != null && start != null && end != null) {
|
let subHeadingText;
|
||||||
console.log({
|
if (start == null) subHeadingText = i18n.t('screens.equipment.booking');
|
||||||
"device": item.id,
|
else if (end != null && start.getTime() !== end.getTime())
|
||||||
"begin": getISODate(start),
|
subHeadingText = i18n.t('screens.equipment.bookingPeriod', {
|
||||||
"end": getISODate(end),
|
begin: getRelativeDateString(start),
|
||||||
})
|
end: getRelativeDateString(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();
|
else
|
||||||
})
|
i18n.t('screens.equipment.bookingDay', {
|
||||||
.catch((error: number) => {
|
date: getRelativeDateString(start),
|
||||||
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) {
|
if (item != null) {
|
||||||
const isAvailable = isEquipmentAvailable(item);
|
const isAvailable = isEquipmentAvailable(item);
|
||||||
const firstAvailability = getFirstEquipmentAvailability(item);
|
const firstAvailability = getFirstEquipmentAvailability(item);
|
||||||
|
@ -280,17 +292,19 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
<Card style={{margin: 5}}>
|
<Card style={{margin: 5}}>
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<View style={{
|
<View
|
||||||
marginLeft: "auto",
|
style={{
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
flexDirection: "row",
|
marginRight: 'auto',
|
||||||
flexWrap: "wrap",
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
}}>
|
}}>
|
||||||
<Headline style={{textAlign: "center"}}>
|
<Headline style={{textAlign: 'center'}}>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Headline>
|
</Headline>
|
||||||
<Caption style={{
|
<Caption
|
||||||
textAlign: "center",
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
lineHeight: 35,
|
lineHeight: 35,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
}}>
|
}}>
|
||||||
|
@ -300,30 +314,24 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={isAvailable ? "check-circle-outline" : "update"}
|
icon={isAvailable ? 'check-circle-outline' : 'update'}
|
||||||
color={isAvailable ? this.props.theme.colors.success : this.props.theme.colors.primary}
|
color={
|
||||||
mode="text"
|
isAvailable
|
||||||
>
|
? props.theme.colors.success
|
||||||
{i18n.t('screens.equipment.available', {date: getRelativeDateString(firstAvailability)})}
|
: props.theme.colors.primary
|
||||||
</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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
mode="text">
|
||||||
|
{i18n.t('screens.equipment.available', {
|
||||||
|
date: getRelativeDateString(firstAvailability),
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
<Subheading
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 10,
|
||||||
|
minHeight: 50,
|
||||||
|
}}>
|
||||||
|
{subHeadingText}
|
||||||
</Subheading>
|
</Subheading>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -335,35 +343,34 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
// Max amount of months allowed to scroll to the future. Default = 50
|
// Max amount of months allowed to scroll to the future. Default = 50
|
||||||
futureScrollRange={3}
|
futureScrollRange={3}
|
||||||
// Enable horizontal scrolling, default = false
|
// Enable horizontal scrolling, default = false
|
||||||
horizontal={true}
|
horizontal
|
||||||
// Enable paging on horizontal, default = false
|
// Enable paging on horizontal, default = false
|
||||||
pagingEnabled={true}
|
pagingEnabled
|
||||||
// Handler which gets executed on day press. Default = undefined
|
// Handler which gets executed on day press. Default = undefined
|
||||||
onDayPress={this.selectNewDate}
|
onDayPress={this.selectNewDate}
|
||||||
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
|
||||||
firstDay={1}
|
firstDay={1}
|
||||||
// Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates
|
// Disable all touch events for disabled days. can be override with disableTouchEvent in markedDates
|
||||||
disableAllTouchEventsForDisabledDays={true}
|
disableAllTouchEventsForDisabledDays
|
||||||
// Hide month navigation arrows.
|
// Hide month navigation arrows.
|
||||||
hideArrows={false}
|
hideArrows={false}
|
||||||
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
|
// Date marking style [simple/period/multi-dot/custom]. Default = 'simple'
|
||||||
markingType={'period'}
|
markingType="period"
|
||||||
markedDates={{...this.lockedDates, ...this.state.markedDates}}
|
markedDates={{...this.lockedDates, ...state.markedDates}}
|
||||||
|
|
||||||
theme={{
|
theme={{
|
||||||
backgroundColor: this.props.theme.colors.agendaBackgroundColor,
|
backgroundColor: props.theme.colors.agendaBackgroundColor,
|
||||||
calendarBackground: this.props.theme.colors.background,
|
calendarBackground: props.theme.colors.background,
|
||||||
textSectionTitleColor: this.props.theme.colors.agendaDayTextColor,
|
textSectionTitleColor: props.theme.colors.agendaDayTextColor,
|
||||||
selectedDayBackgroundColor: this.props.theme.colors.primary,
|
selectedDayBackgroundColor: props.theme.colors.primary,
|
||||||
selectedDayTextColor: '#ffffff',
|
selectedDayTextColor: '#ffffff',
|
||||||
todayTextColor: this.props.theme.colors.text,
|
todayTextColor: props.theme.colors.text,
|
||||||
dayTextColor: this.props.theme.colors.text,
|
dayTextColor: props.theme.colors.text,
|
||||||
textDisabledColor: this.props.theme.colors.agendaDayTextColor,
|
textDisabledColor: props.theme.colors.agendaDayTextColor,
|
||||||
dotColor: this.props.theme.colors.primary,
|
dotColor: props.theme.colors.primary,
|
||||||
selectedDotColor: '#ffffff',
|
selectedDotColor: '#ffffff',
|
||||||
arrowColor: this.props.theme.colors.primary,
|
arrowColor: props.theme.colors.primary,
|
||||||
monthTextColor: this.props.theme.colors.text,
|
monthTextColor: props.theme.colors.text,
|
||||||
indicatorColor: this.props.theme.colors.primary,
|
indicatorColor: props.theme.colors.primary,
|
||||||
textDayFontFamily: 'monospace',
|
textDayFontFamily: 'monospace',
|
||||||
textMonthFontFamily: 'monospace',
|
textMonthFontFamily: 'monospace',
|
||||||
textDayHeaderFontFamily: 'monospace',
|
textDayHeaderFontFamily: 'monospace',
|
||||||
|
@ -379,15 +386,14 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
height: 34,
|
height: 34,
|
||||||
width: 34,
|
width: 34,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
style={{marginBottom: 50}}
|
style={{marginBottom: 50}}
|
||||||
/>
|
/>
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
<LoadingConfirmDialog
|
<LoadingConfirmDialog
|
||||||
visible={this.state.dialogVisible}
|
visible={state.dialogVisible}
|
||||||
onDismiss={this.onDialogDismiss}
|
onDismiss={this.onDialogDismiss}
|
||||||
onAccept={this.onDialogAccept}
|
onAccept={this.onDialogAccept}
|
||||||
title={i18n.t('screens.equipment.dialogTitle')}
|
title={i18n.t('screens.equipment.dialogTitle')}
|
||||||
|
@ -396,46 +402,40 @@ class EquipmentRentScreen extends React.Component<Props, State> {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ErrorDialog
|
<ErrorDialog
|
||||||
visible={this.state.errorDialogVisible}
|
visible={state.errorDialogVisible}
|
||||||
onDismiss={this.onErrorDialogDismiss}
|
onDismiss={this.onErrorDialogDismiss}
|
||||||
errorCode={this.state.currentError}
|
errorCode={state.currentError}
|
||||||
/>
|
/>
|
||||||
<Animatable.View
|
<Animatable.View
|
||||||
ref={this.bookRef}
|
ref={this.bookRef}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
width: "100%",
|
width: '100%',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
transform: [
|
transform: [{translateY: 100}],
|
||||||
{translateY: 100},
|
|
||||||
]
|
|
||||||
}}>
|
}}>
|
||||||
<Button
|
<Button
|
||||||
icon="bookmark-check"
|
icon="bookmark-check"
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={this.showDialog}
|
onPress={this.showDialog}
|
||||||
style={{
|
style={{
|
||||||
width: "80%",
|
width: '80%',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginLeft: "auto",
|
marginLeft: 'auto',
|
||||||
marginRight: "auto",
|
marginRight: 'auto',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
borderRadius: 10
|
borderRadius: 10,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
{i18n.t('screens.equipment.bookButton')}
|
{i18n.t('screens.equipment.bookButton')}
|
||||||
</Button>
|
</Button>
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
|
|
||||||
</View>
|
</View>
|
||||||
|
);
|
||||||
)
|
}
|
||||||
} else
|
return null;
|
||||||
return <View/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(EquipmentRentScreen);
|
export default withTheme(EquipmentRentScreen);
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {Device} from "../screens/Amicale/Equipment/EquipmentListScreen";
|
import i18n from 'i18n-js';
|
||||||
import i18n from "i18n-js";
|
import type {DeviceType} from '../screens/Amicale/Equipment/EquipmentListScreen';
|
||||||
import DateManager from "../managers/DateManager";
|
import DateManager from '../managers/DateManager';
|
||||||
import type {CustomTheme} from "../managers/ThemeManager";
|
import type {CustomTheme} from '../managers/ThemeManager';
|
||||||
|
import type {MarkedDatesObjectType} from '../screens/Amicale/Equipment/EquipmentRentScreen';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current day at midnight
|
* Gets the current day at midnight
|
||||||
*
|
*
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
export function getCurrentDay() {
|
export function getCurrentDay(): Date {
|
||||||
let today = new Date(Date.now());
|
const today = new Date(Date.now());
|
||||||
today.setUTCHours(0, 0, 0, 0);
|
today.setUTCHours(0, 0, 0, 0);
|
||||||
return today;
|
return today;
|
||||||
}
|
}
|
||||||
|
@ -22,8 +23,8 @@ export function getCurrentDay() {
|
||||||
* @param date The date to recover the ISO format from
|
* @param date The date to recover the ISO format from
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
export function getISODate(date: Date) {
|
export function getISODate(date: Date): string {
|
||||||
return date.toISOString().split("T")[0];
|
return date.toISOString().split('T')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,17 +33,15 @@ export function getISODate(date: Date) {
|
||||||
* @param item
|
* @param item
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isEquipmentAvailable(item: Device) {
|
export function isEquipmentAvailable(item: DeviceType): boolean {
|
||||||
let isAvailable = true;
|
let isAvailable = true;
|
||||||
const today = getCurrentDay();
|
const today = getCurrentDay();
|
||||||
const dates = item.booked_at;
|
const dates = item.booked_at;
|
||||||
for (let i = 0; i < dates.length; i++) {
|
dates.forEach((date: {begin: string, end: string}) => {
|
||||||
const start = new Date(dates[i].begin);
|
const start = new Date(date.begin);
|
||||||
const end = new Date(dates[i].end);
|
const end = new Date(date.end);
|
||||||
isAvailable = today < start || today > end;
|
if (!(today < start || today > end)) isAvailable = false;
|
||||||
if (!isAvailable)
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
return isAvailable;
|
return isAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,16 +51,15 @@ export function isEquipmentAvailable(item: Device) {
|
||||||
* @param item
|
* @param item
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
export function getFirstEquipmentAvailability(item: Device) {
|
export function getFirstEquipmentAvailability(item: DeviceType): Date {
|
||||||
let firstAvailability = getCurrentDay();
|
let firstAvailability = getCurrentDay();
|
||||||
const dates = item.booked_at;
|
const dates = item.booked_at;
|
||||||
for (let i = 0; i < dates.length; i++) {
|
dates.forEach((date: {begin: string, end: string}) => {
|
||||||
const start = new Date(dates[i].begin);
|
const start = new Date(date.begin);
|
||||||
let end = new Date(dates[i].end);
|
const end = new Date(date.end);
|
||||||
end.setDate(end.getDate() + 1);
|
end.setDate(end.getDate() + 1);
|
||||||
if (firstAvailability >= start)
|
if (firstAvailability >= start) firstAvailability = end;
|
||||||
firstAvailability = end;
|
});
|
||||||
}
|
|
||||||
return firstAvailability;
|
return firstAvailability;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ export function getFirstEquipmentAvailability(item: Device) {
|
||||||
*
|
*
|
||||||
* @param date The date to translate
|
* @param date The date to translate
|
||||||
*/
|
*/
|
||||||
export function getRelativeDateString(date: Date) {
|
export function getRelativeDateString(date: Date): string {
|
||||||
const today = getCurrentDay();
|
const today = getCurrentDay();
|
||||||
const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
|
const yearDelta = date.getUTCFullYear() - today.getUTCFullYear();
|
||||||
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
|
const monthDelta = date.getUTCMonth() - today.getUTCMonth();
|
||||||
|
@ -80,7 +78,7 @@ export function getRelativeDateString(date: Date) {
|
||||||
translatedString = i18n.t('screens.equipment.otherYear', {
|
translatedString = i18n.t('screens.equipment.otherYear', {
|
||||||
date: date.getDate(),
|
date: date.getDate(),
|
||||||
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
month: DateManager.getInstance().getMonthsOfYear()[date.getMonth()],
|
||||||
year: date.getFullYear()
|
year: date.getFullYear(),
|
||||||
});
|
});
|
||||||
else if (monthDelta > 0)
|
else if (monthDelta > 0)
|
||||||
translatedString = i18n.t('screens.equipment.otherMonth', {
|
translatedString = i18n.t('screens.equipment.otherMonth', {
|
||||||
|
@ -111,13 +109,17 @@ export function getRelativeDateString(date: Date) {
|
||||||
* @param item Item containing booked dates to look for
|
* @param item Item containing booked dates to look for
|
||||||
* @returns {[string]}
|
* @returns {[string]}
|
||||||
*/
|
*/
|
||||||
export function getValidRange(start: Date, end: Date, item: Device | null) {
|
export function getValidRange(
|
||||||
let direction = start <= end ? 1 : -1;
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
item: DeviceType | null,
|
||||||
|
): Array<string> {
|
||||||
|
const direction = start <= end ? 1 : -1;
|
||||||
let limit = new Date(end);
|
let limit = new Date(end);
|
||||||
limit.setDate(limit.getDate() + direction); // Limit is excluded, but we want to include range end
|
limit.setDate(limit.getDate() + direction); // Limit is excluded, but we want to include range end
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
if (direction === 1) {
|
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);
|
const bookLimit = new Date(item.booked_at[i].begin);
|
||||||
if (start < bookLimit && limit > bookLimit) {
|
if (start < bookLimit && limit > bookLimit) {
|
||||||
limit = bookLimit;
|
limit = bookLimit;
|
||||||
|
@ -125,7 +127,7 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
const bookLimit = new Date(item.booked_at[i].end);
|
||||||
if (start > bookLimit && limit < bookLimit) {
|
if (start > bookLimit && limit < bookLimit) {
|
||||||
limit = bookLimit;
|
limit = bookLimit;
|
||||||
|
@ -135,14 +137,14 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validRange = [];
|
||||||
let validRange = [];
|
const date = new Date(start);
|
||||||
let date = new Date(start);
|
while (
|
||||||
while ((direction === 1 && date < limit) || (direction === -1 && date > limit)) {
|
(direction === 1 && date < limit) ||
|
||||||
if (direction === 1)
|
(direction === -1 && date > limit)
|
||||||
validRange.push(getISODate(date));
|
) {
|
||||||
else
|
if (direction === 1) validRange.push(getISODate(date));
|
||||||
validRange.unshift(getISODate(date));
|
else validRange.unshift(getISODate(date));
|
||||||
date.setDate(date.getDate() + direction);
|
date.setDate(date.getDate() + direction);
|
||||||
}
|
}
|
||||||
return validRange;
|
return validRange;
|
||||||
|
@ -157,19 +159,23 @@ export function getValidRange(start: Date, end: Date, item: Device | null) {
|
||||||
* @param range The range to mark dates for
|
* @param range The range to mark dates for
|
||||||
* @returns {{}}
|
* @returns {{}}
|
||||||
*/
|
*/
|
||||||
export function generateMarkedDates(isSelection: boolean, theme: CustomTheme, range: Array<string>) {
|
export function generateMarkedDates(
|
||||||
let markedDates = {}
|
isSelection: boolean,
|
||||||
for (let i = 0; i < range.length; i++) {
|
theme: CustomTheme,
|
||||||
|
range: Array<string>,
|
||||||
|
): MarkedDatesObjectType {
|
||||||
|
const markedDates = {};
|
||||||
|
for (let i = 0; i < range.length; i += 1) {
|
||||||
const isStart = i === 0;
|
const isStart = i === 0;
|
||||||
const isEnd = i === range.length - 1;
|
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]] = {
|
markedDates[range[i]] = {
|
||||||
startingDay: isStart,
|
startingDay: isStart,
|
||||||
endingDay: isEnd,
|
endingDay: isEnd,
|
||||||
color: isSelection
|
color,
|
||||||
? isStart || isEnd
|
|
||||||
? theme.colors.primary
|
|
||||||
: theme.colors.danger
|
|
||||||
: theme.colors.textDisabled
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return markedDates;
|
return markedDates;
|
||||||
|
|
Loading…
Reference in a new issue