// @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"; type Props = { navigation: StackNavigationProp, route: { params?: { item?: Device, }, }, theme: CustomTheme, } type State = { dialogVisible: boolean, errorDialogVisible: boolean, markedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } }, currentError: number, } class EquipmentRentScreen extends React.Component { state = { dialogVisible: false, errorDialogVisible: false, markedDates: {}, currentError: 0, } item: Device | null; bookedDates: Array; bookRef: { current: null | Animatable.View } canBookEquipment: boolean; lockedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } } constructor(props: Props) { super(props); this.resetSelection(); this.bookRef = React.createRef(); this.canBookEquipment = false; this.bookedDates = []; if (this.props.route.params != null) { if (this.props.route.params.item != null) this.item = this.props.route.params.item; else this.item = null; } 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 ) }; } } } /** * 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; }; /** * 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(); } } updateSelectionRange(start: Date, end: Date) { this.bookedDates = getValidRange(start, end, this.item); } updateMarkedSelection() { this.setState({ markedDates: generateMarkedDates( true, this.props.theme, this.bookedDates ), }); } enableBooking() { if (!this.canBookEquipment) { 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 */ 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); } } showDialog = () => { this.setState({dialogVisible: true}); } showErrorDialog = (error: number) => { this.setState({ errorDialogVisible: true, currentError: error, }); } 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} */ 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 ( {item.name} ({i18n.t('screens.equipment.bail', {cost: item.caution})}) { 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) }) } ) } else return ; } } export default withTheme(EquipmentRentScreen);