123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441 |
- // @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<Props, State> {
-
- state = {
- dialogVisible: false,
- errorDialogVisible: false,
- markedDates: {},
- currentError: 0,
- }
-
- item: Device | null;
- bookedDates: Array<string>;
-
- bookRef: { current: null | Animatable.View }
- canBookEquipment: boolean;
-
- lockedDates: { [key: string]: { startingDay: boolean, endingDay: boolean, color: string } }
-
- constructor(props: Props) {
- super(props);
- this.resetSelection();
- this.bookRef = React.createRef();
- this.canBookEquipment = false;
- this.bookedDates = [];
- if (this.props.route.params != null) {
- if (this.props.route.params.item != null)
- this.item = this.props.route.params.item;
- else
- this.item = null;
- }
- 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<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 (
- <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>
-
- )
- } else
- return <View/>;
- }
-
- }
-
- export default withTheme(EquipmentRentScreen);
|