Improve Planning screen components to match linter

This commit is contained in:
Arnaud Vergnet 2020-08-05 15:04:41 +02:00
parent a3299c19f7
commit 3ce23726c2
4 changed files with 586 additions and 537 deletions

View file

@ -2,166 +2,183 @@
import * as React from 'react';
import {View} from 'react-native';
import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
import {Card, withTheme} from 'react-native-paper';
import DateManager from "../../managers/DateManager";
import ImageModal from 'react-native-image-modal';
import BasicLoadingScreen from "../../components/Screens/BasicLoadingScreen";
import {apiRequest, ERROR_TYPE} from "../../utils/WebData";
import ErrorView from "../../components/Screens/ErrorView";
import CustomHTML from "../../components/Overrides/CustomHTML";
import CustomTabBar from "../../components/Tabbar/CustomTabBar";
import i18n from 'i18n-js';
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager";
import CollapsibleScrollView from "../../components/Collapsible/CollapsibleScrollView";
import {StackNavigationProp} from '@react-navigation/stack';
import {getDateOnlyString, getFormattedEventTime} from '../../utils/Planning';
import DateManager from '../../managers/DateManager';
import BasicLoadingScreen from '../../components/Screens/BasicLoadingScreen';
import {apiRequest, ERROR_TYPE} from '../../utils/WebData';
import ErrorView from '../../components/Screens/ErrorView';
import CustomHTML from '../../components/Overrides/CustomHTML';
import CustomTabBar from '../../components/Tabbar/CustomTabBar';
import type {CustomThemeType} from '../../managers/ThemeManager';
import CollapsibleScrollView from '../../components/Collapsible/CollapsibleScrollView';
import type {PlanningEventType} from '../../utils/Planning';
type Props = {
navigation: StackNavigationProp,
route: { params: { data: Object, id: number, eventId: number } },
theme: CustomTheme
type PropsType = {
navigation: StackNavigationProp,
route: {params: {data: PlanningEventType, id: number, eventId: number}},
theme: CustomThemeType,
};
type State = {
loading: boolean
type StateType = {
loading: boolean,
};
const CLUB_INFO_PATH = "event/info";
const EVENT_INFO_URL = 'event/info';
/**
* Class defining a planning event information page.
*/
class PlanningDisplayScreen extends React.Component<Props, State> {
class PlanningDisplayScreen extends React.Component<PropsType, StateType> {
displayData: null | PlanningEventType;
displayData: Object;
shouldFetchData: boolean;
eventId: number;
errorCode: number;
shouldFetchData: boolean;
/**
* Generates data depending on whether the screen was opened from the planning or from a link
*
* @param props
*/
constructor(props) {
super(props);
eventId: number;
if (this.props.route.params.data != null) {
this.displayData = this.props.route.params.data;
this.eventId = this.displayData.id;
this.shouldFetchData = false;
this.errorCode = 0;
this.state = {
loading: false,
};
} else {
this.displayData = null;
this.eventId = this.props.route.params.eventId;
this.shouldFetchData = true;
this.errorCode = 0;
this.state = {
loading: true,
};
this.fetchData();
errorCode: number;
}
/**
* Generates data depending on whether the screen was opened from the planning or from a link
*
* @param props
*/
constructor(props: PropsType) {
super(props);
if (props.route.params.data != null) {
this.displayData = props.route.params.data;
this.eventId = this.displayData.id;
this.shouldFetchData = false;
this.errorCode = 0;
this.state = {
loading: false,
};
} else {
this.displayData = null;
this.eventId = props.route.params.eventId;
this.shouldFetchData = true;
this.errorCode = 0;
this.state = {
loading: true,
};
this.fetchData();
}
}
/**
* Fetches data for the current event id from the API
*/
fetchData = () => {
this.setState({loading: true});
apiRequest(CLUB_INFO_PATH, 'POST', {id: this.eventId})
.then(this.onFetchSuccess)
.catch(this.onFetchError);
};
/**
* Hides loading and saves fetched data
*
* @param data Received data
*/
onFetchSuccess = (data: PlanningEventType) => {
this.displayData = data;
this.setState({loading: false});
};
/**
* Hides loading and saves fetched data
*
* @param data Received data
*/
onFetchSuccess = (data: Object) => {
this.displayData = data;
this.setState({loading: false});
};
/**
* Hides loading and saves the error code
*
* @param error
*/
onFetchError = (error: number) => {
this.errorCode = error;
this.setState({loading: false});
};
/**
* Hides loading and saves the error code
*
* @param error
*/
onFetchError = (error: number) => {
this.errorCode = error;
this.setState({loading: false});
};
/**
* Gets content to display
*
* @returns {*}
*/
getContent(): React.Node {
const {theme} = this.props;
const {displayData} = this;
if (displayData == null) return null;
let subtitle = getFormattedEventTime(
displayData.date_begin,
displayData.date_end,
);
const dateString = getDateOnlyString(displayData.date_begin);
if (dateString !== null)
subtitle += ` | ${DateManager.getInstance().getTranslatedDate(
dateString,
)}`;
return (
<CollapsibleScrollView style={{paddingLeft: 5, paddingRight: 5}} hasTab>
<Card.Title title={displayData.title} subtitle={subtitle} />
{displayData.logo !== null ? (
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor={theme.colors.background}
style={{
width: 300,
height: 300,
}}
source={{
uri: displayData.logo,
}}
/>
</View>
) : null}
/**
* Gets content to display
*
* @returns {*}
*/
getContent() {
let subtitle = getFormattedEventTime(
this.displayData["date_begin"], this.displayData["date_end"]);
let dateString = getDateOnlyString(this.displayData["date_begin"]);
if (dateString !== null)
subtitle += ' | ' + DateManager.getInstance().getTranslatedDate(dateString);
return (
<CollapsibleScrollView
style={{paddingLeft: 5, paddingRight: 5}}
hasTab={true}
>
<Card.Title
title={this.displayData.title}
subtitle={subtitle}
/>
{this.displayData.logo !== null ?
<View style={{marginLeft: 'auto', marginRight: 'auto'}}>
<ImageModal
resizeMode="contain"
imageBackgroundColor={this.props.theme.colors.background}
style={{
width: 300,
height: 300,
}}
source={{
uri: this.displayData.logo,
}}
/></View>
: <View/>}
{displayData.description !== null ? (
<Card.Content
style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
<CustomHTML html={displayData.description} />
</Card.Content>
) : (
<View />
)}
</CollapsibleScrollView>
);
}
{this.displayData.description !== null ?
<Card.Content style={{paddingBottom: CustomTabBar.TAB_BAR_HEIGHT + 20}}>
<CustomHTML html={this.displayData.description}/>
</Card.Content>
: <View/>}
</CollapsibleScrollView>
);
}
/**
* Shows an error view and use a custom message if the event does not exist
*
* @returns {*}
*/
getErrorView(): React.Node {
const {navigation} = this.props;
if (this.errorCode === ERROR_TYPE.BAD_INPUT)
return (
<ErrorView
navigation={navigation}
showRetryButton={false}
message={i18n.t('screens.planning.invalidEvent')}
icon="calendar-remove"
/>
);
return (
<ErrorView
navigation={navigation}
errorCode={this.errorCode}
onRefresh={this.fetchData}
/>
);
}
/**
* Shows an error view and use a custom message if the event does not exist
*
* @returns {*}
*/
getErrorView() {
if (this.errorCode === ERROR_TYPE.BAD_INPUT)
return <ErrorView {...this.props} showRetryButton={false} message={i18n.t("screens.planning.invalidEvent")}
icon={"calendar-remove"}/>;
else
return <ErrorView {...this.props} errorCode={this.errorCode} onRefresh={this.fetchData}/>;
}
/**
* Fetches data for the current event id from the API
*/
fetchData = () => {
this.setState({loading: true});
apiRequest(EVENT_INFO_URL, 'POST', {id: this.eventId})
.then(this.onFetchSuccess)
.catch(this.onFetchError);
};
render() {
if (this.state.loading)
return <BasicLoadingScreen/>;
else if (this.errorCode === 0)
return this.getContent();
else
return this.getErrorView();
}
render(): React.Node {
const {loading} = this.state;
if (loading) return <BasicLoadingScreen />;
if (this.errorCode === 0) return this.getContent();
return this.getErrorView();
}
}
export default withTheme(PlanningDisplayScreen);

View file

@ -2,259 +2,282 @@
import * as React from 'react';
import {BackHandler, View} from 'react-native';
import i18n from "i18n-js";
import {LocaleConfig} from 'react-native-calendars';
import {readData} from "../../utils/WebData";
import type {eventObject} from "../../utils/Planning";
import {
generateEventAgenda,
getCurrentDateString,
getDateOnlyString,
getFormattedEventTime,
} from '../../utils/Planning';
import i18n from 'i18n-js';
import {Agenda, LocaleConfig} from 'react-native-calendars';
import {Avatar, Divider, List} from 'react-native-paper';
import CustomAgenda from "../../components/Overrides/CustomAgenda";
import {StackNavigationProp} from "@react-navigation/stack";
import {MASCOT_STYLE} from "../../components/Mascot/Mascot";
import MascotPopup from "../../components/Mascot/MascotPopup";
import AsyncStorageManager from "../../managers/AsyncStorageManager";
import {StackNavigationProp} from '@react-navigation/stack';
import {readData} from '../../utils/WebData';
import type {PlanningEventType} from '../../utils/Planning';
import {
generateEventAgenda,
getCurrentDateString,
getDateOnlyString,
getFormattedEventTime,
} from '../../utils/Planning';
import CustomAgenda from '../../components/Overrides/CustomAgenda';
import {MASCOT_STYLE} from '../../components/Mascot/Mascot';
import MascotPopup from '../../components/Mascot/MascotPopup';
import AsyncStorageManager from '../../managers/AsyncStorageManager';
LocaleConfig.locales['fr'] = {
monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
monthNamesShort: ['Janv.', 'Févr.', 'Mars', 'Avril', 'Mai', 'Juin', 'Juil.', 'Août', 'Sept.', 'Oct.', 'Nov.', 'Déc.'],
dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
today: 'Aujourd\'hui'
LocaleConfig.locales.fr = {
monthNames: [
'Janvier',
'Février',
'Mars',
'Avril',
'Mai',
'Juin',
'Juillet',
'Août',
'Septembre',
'Octobre',
'Novembre',
'Décembre',
],
monthNamesShort: [
'Janv.',
'Févr.',
'Mars',
'Avril',
'Mai',
'Juin',
'Juil.',
'Août',
'Sept.',
'Oct.',
'Nov.',
'Déc.',
],
dayNames: [
'Dimanche',
'Lundi',
'Mardi',
'Mercredi',
'Jeudi',
'Vendredi',
'Samedi',
],
dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
today: "Aujourd'hui",
};
type Props = {
navigation: StackNavigationProp,
}
type State = {
refreshing: boolean,
agendaItems: Object,
calendarShowing: boolean,
type PropsType = {
navigation: StackNavigationProp,
};
const FETCH_URL = "https://www.amicale-insat.fr/api/event/list";
type StateType = {
refreshing: boolean,
agendaItems: {[key: string]: Array<PlanningEventType>},
calendarShowing: boolean,
};
const FETCH_URL = 'https://www.amicale-insat.fr/api/event/list';
const AGENDA_MONTH_SPAN = 3;
/**
* Class defining the app's planning screen
*/
class PlanningScreen extends React.Component<Props, State> {
class PlanningScreen extends React.Component<PropsType, StateType> {
agendaRef: null | Agenda;
agendaRef: Object;
lastRefresh: Date;
lastRefresh: Date;
minTimeBetweenRefresh = 60;
minTimeBetweenRefresh = 60;
state = {
refreshing: false,
agendaItems: {},
calendarShowing: false,
currentDate = getDateOnlyString(getCurrentDateString());
constructor(props: PropsType) {
super(props);
if (i18n.currentLocale().startsWith('fr')) {
LocaleConfig.defaultLocale = 'fr';
}
this.state = {
refreshing: false,
agendaItems: {},
calendarShowing: false,
};
}
currentDate = getDateOnlyString(getCurrentDateString());
/**
* Captures focus and blur events to hook on android back button
*/
componentDidMount() {
const {navigation} = this.props;
this.onRefresh();
navigation.addListener('focus', () => {
BackHandler.addEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid,
);
});
navigation.addListener('blur', () => {
BackHandler.removeEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid,
);
});
}
constructor(props: any) {
super(props);
if (i18n.currentLocale().startsWith("fr")) {
LocaleConfig.defaultLocale = 'fr';
}
/**
* Overrides default android back button behaviour to close the calendar if it was open.
*
* @return {boolean}
*/
onBackButtonPressAndroid = (): boolean => {
const {calendarShowing} = this.state;
if (calendarShowing && this.agendaRef != null) {
this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
return true;
}
return false;
};
/**
* Captures focus and blur events to hook on android back button
*/
componentDidMount() {
this.onRefresh();
this.props.navigation.addListener(
'focus',
() =>
BackHandler.addEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid
)
);
this.props.navigation.addListener(
'blur',
() =>
BackHandler.removeEventListener(
'hardwareBackPress',
this.onBackButtonPressAndroid
)
);
/**
* Refreshes data and shows an animation while doing it
*/
onRefresh = () => {
let canRefresh;
if (this.lastRefresh !== undefined)
canRefresh =
(new Date().getTime() - this.lastRefresh.getTime()) / 1000 >
this.minTimeBetweenRefresh;
else canRefresh = true;
if (canRefresh) {
this.setState({refreshing: true});
readData(FETCH_URL)
.then((fetchedData: Array<PlanningEventType>) => {
this.setState({
refreshing: false,
agendaItems: generateEventAgenda(fetchedData, AGENDA_MONTH_SPAN),
});
this.lastRefresh = new Date();
})
.catch(() => {
this.setState({
refreshing: false,
});
});
}
};
/**
* Overrides default android back button behaviour to close the calendar if it was open.
*
* @return {boolean}
*/
onBackButtonPressAndroid = () => {
if (this.state.calendarShowing) {
this.agendaRef.chooseDay(this.agendaRef.state.selectedDay);
return true;
} else {
return false;
}
/**
* Callback used when receiving the agenda ref
*
* @param ref
*/
onAgendaRef = (ref: Agenda) => {
this.agendaRef = ref;
};
/**
* Callback used when a button is pressed to toggle the calendar
*
* @param isCalendarOpened True is the calendar is already open, false otherwise
*/
onCalendarToggled = (isCalendarOpened: boolean) => {
this.setState({calendarShowing: isCalendarOpened});
};
/**
* Gets an event render item
*
* @param item The current event to render
* @return {*}
*/
getRenderItem = (item: PlanningEventType): React.Node => {
const {navigation} = this.props;
const onPress = () => {
navigation.navigate('planning-information', {
data: item,
});
};
/**
* Function used to check if a row has changed
*
* @param r1
* @param r2
* @return {boolean}
*/
rowHasChanged(r1: Object, r2: Object) {
return false;
// if (r1 !== undefined && r2 !== undefined)
// return r1.title !== r2.title;
// else return !(r1 === undefined && r2 === undefined);
if (item.logo !== null) {
return (
<View>
<Divider />
<List.Item
title={item.title}
description={getFormattedEventTime(item.date_begin, item.date_end)}
left={(): React.Node => (
<Avatar.Image
source={{uri: item.logo}}
style={{backgroundColor: 'transparent'}}
/>
)}
onPress={onPress}
/>
</View>
);
}
return (
<View>
<Divider />
<List.Item
title={item.title}
description={getFormattedEventTime(item.date_begin, item.date_end)}
onPress={onPress}
/>
</View>
);
};
/**
* Refreshes data and shows an animation while doing it
*/
onRefresh = () => {
let canRefresh;
if (this.lastRefresh !== undefined)
canRefresh = (new Date().getTime() - this.lastRefresh.getTime()) / 1000 > this.minTimeBetweenRefresh;
else
canRefresh = true;
/**
* Gets an empty render item for an empty date
*
* @return {*}
*/
getRenderEmptyDate = (): React.Node => <Divider />;
if (canRefresh) {
this.setState({refreshing: true});
readData(FETCH_URL)
.then((fetchedData) => {
this.setState({
refreshing: false,
agendaItems: generateEventAgenda(fetchedData, AGENDA_MONTH_SPAN)
});
this.lastRefresh = new Date();
})
.catch(() => {
this.setState({
refreshing: false,
});
});
}
};
/**
* Callback used when receiving the agenda ref
*
* @param ref
*/
onAgendaRef = (ref: Object) => {
this.agendaRef = ref;
}
/**
* Callback used when a button is pressed to toggle the calendar
*
* @param isCalendarOpened True is the calendar is already open, false otherwise
*/
onCalendarToggled = (isCalendarOpened: boolean) => {
this.setState({calendarShowing: isCalendarOpened});
}
/**
* Gets an event render item
*
* @param item The current event to render
* @return {*}
*/
getRenderItem = (item: eventObject) => {
const onPress = this.props.navigation.navigate.bind(this, 'planning-information', {data: item});
if (item.logo !== null) {
return (
<View>
<Divider/>
<List.Item
title={item.title}
description={getFormattedEventTime(item["date_begin"], item["date_end"])}
left={() => <Avatar.Image
source={{uri: item.logo}}
style={{backgroundColor: 'transparent'}}
/>}
onPress={onPress}
/>
</View>
);
} else {
return (
<View>
<Divider/>
<List.Item
title={item.title}
description={getFormattedEventTime(item["date_begin"], item["date_end"])}
onPress={onPress}
/>
</View>
);
}
}
/**
* Gets an empty render item for an empty date
*
* @return {*}
*/
getRenderEmptyDate = () => <Divider/>;
render() {
return (
<View style={{flex: 1}}>
<CustomAgenda
{...this.props}
// the list of items that have to be displayed in agenda. If you want to render item as empty date
// the value of date key kas to be an empty array []. If there exists no value for date key it is
// considered that the date in question is not yet loaded
items={this.state.agendaItems}
// initially selected day
selected={this.currentDate}
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
minDate={this.currentDate}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={1}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={AGENDA_MONTH_SPAN}
// If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
onRefresh={this.onRefresh}
// callback that fires when the calendar is opened or closed
onCalendarToggled={this.onCalendarToggled}
// Set this true while waiting for new data from a refresh
refreshing={this.state.refreshing}
renderItem={this.getRenderItem}
renderEmptyDate={this.getRenderEmptyDate}
rowHasChanged={this.rowHasChanged}
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
firstDay={1}
// ref to this agenda in order to handle back button event
onRef={this.onAgendaRef}
/>
<MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
title={i18n.t("screens.planning.mascotDialog.title")}
message={i18n.t("screens.planning.mascotDialog.message")}
icon={"party-popper"}
buttons={{
action: null,
cancel: {
message: i18n.t("screens.planning.mascotDialog.button"),
icon: "check",
}
}}
emotion={MASCOT_STYLE.HAPPY}
/>
</View>
);
}
render(): React.Node {
const {state, props} = this;
return (
<View style={{flex: 1}}>
<CustomAgenda
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
// the list of items that have to be displayed in agenda. If you want to render item as empty date
// the value of date key kas to be an empty array []. If there exists no value for date key it is
// considered that the date in question is not yet loaded
items={state.agendaItems}
// initially selected day
selected={this.currentDate}
// Minimum date that can be selected, dates before minDate will be grayed out. Default = undefined
minDate={this.currentDate}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={1}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={AGENDA_MONTH_SPAN}
// If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop correctly.
onRefresh={this.onRefresh}
// callback that fires when the calendar is opened or closed
onCalendarToggled={this.onCalendarToggled}
// Set this true while waiting for new data from a refresh
refreshing={state.refreshing}
renderItem={this.getRenderItem}
renderEmptyDate={this.getRenderEmptyDate}
// If firstDay=1 week starts from Monday. Note that dayNames and dayNamesShort should still start from Sunday.
firstDay={1}
// ref to this agenda in order to handle back button event
onRef={this.onAgendaRef}
/>
<MascotPopup
prefKey={AsyncStorageManager.PREFERENCES.eventsShowBanner.key}
title={i18n.t('screens.planning.mascotDialog.title')}
message={i18n.t('screens.planning.mascotDialog.message')}
icon="party-popper"
buttons={{
action: null,
cancel: {
message: i18n.t('screens.planning.mascotDialog.button'),
icon: 'check',
},
}}
emotion={MASCOT_STYLE.HAPPY}
/>
</View>
);
}
}
export default PlanningScreen;

View file

@ -1,74 +1,20 @@
// @flow
export type eventObject = {
id: number,
title: string,
logo: string,
date_begin: string,
date_end: string,
description: string,
club: string,
category_id: number,
url: string,
export type PlanningEventType = {
id: number,
title: string,
logo: string,
date_begin: string,
date_end: string,
description: string,
club: string,
category_id: number,
url: string,
};
// Regex used to check date string validity
const dateRegExp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
/**
* Gets the current day string representation in the format
* YYYY-MM-DD
*
* @return {string} The string representation
*/
export function getCurrentDateString(): string {
return dateToString(new Date(Date.now()), false);
}
/**
* Checks if the given date is before the other.
*
* @param event1Date Event 1 date in format YYYY-MM-DD HH:MM
* @param event2Date Event 2 date in format YYYY-MM-DD HH:MM
* @return {boolean}
*/
export function isEventBefore(event1Date: string, event2Date: string): boolean {
let date1 = stringToDate(event1Date);
let date2 = stringToDate(event2Date);
if (date1 !== null && date2 !== null)
return date1 < date2;
else
return false;
}
/**
* Gets only the date part of the given event date string in the format
* YYYY-MM-DD HH:MM
*
* @param dateString The string to get the date from
* @return {string|null} Date in format YYYY:MM:DD or null if given string is invalid
*/
export function getDateOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString))
return dateString.split(" ")[0];
else
return null;
}
/**
* Gets only the time part of the given event date string in the format
* YYYY-MM-DD HH:MM
*
* @param dateString The string to get the date from
* @return {string|null} Time in format HH:MM or null if given string is invalid
*/
export function getTimeOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString))
return dateString.split(" ")[1];
else
return null;
}
/**
* Checks if the given date string is in the format
* YYYY-MM-DD HH:MM
@ -77,9 +23,11 @@ export function getTimeOnlyString(dateString: string): string | null {
* @return {boolean}
*/
export function isEventDateStringFormatValid(dateString: ?string): boolean {
return dateString !== undefined
&& dateString !== null
&& dateRegExp.test(dateString);
return (
dateString !== undefined &&
dateString !== null &&
dateRegExp.test(dateString)
);
}
/**
@ -90,26 +38,20 @@ export function isEventDateStringFormatValid(dateString: ?string): boolean {
* @return {Date|null} The date object or null if the given string is invalid
*/
export function stringToDate(dateString: string): Date | null {
let date = new Date();
if (isEventDateStringFormatValid(dateString)) {
let stringArray = dateString.split(' ');
let dateArray = stringArray[0].split('-');
let timeArray = stringArray[1].split(':');
date.setFullYear(
parseInt(dateArray[0]),
parseInt(dateArray[1]) - 1, // Month range from 0 to 11
parseInt(dateArray[2])
);
date.setHours(
parseInt(timeArray[0]),
parseInt(timeArray[1]),
0,
0,
);
} else
date = null;
let date = new Date();
if (isEventDateStringFormatValid(dateString)) {
const stringArray = dateString.split(' ');
const dateArray = stringArray[0].split('-');
const timeArray = stringArray[1].split(':');
date.setFullYear(
parseInt(dateArray[0], 10),
parseInt(dateArray[1], 10) - 1, // Month range from 0 to 11
parseInt(dateArray[2], 10),
);
date.setHours(parseInt(timeArray[0], 10), parseInt(timeArray[1], 10), 0, 0);
} else date = null;
return date;
return date;
}
/**
@ -121,13 +63,61 @@ export function stringToDate(dateString: string): Date | null {
* @return {string} The converted string
*/
export function dateToString(date: Date, isUTC: boolean): string {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0'); //January is 0!
const year = date.getFullYear();
const h = isUTC ? date.getUTCHours() : date.getHours();
const hours = String(h).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes;
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0'); // January is 0!
const year = date.getFullYear();
const h = isUTC ? date.getUTCHours() : date.getHours();
const hours = String(h).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
/**
* Gets the current day string representation in the format
* YYYY-MM-DD
*
* @return {string} The string representation
*/
export function getCurrentDateString(): string {
return dateToString(new Date(Date.now()), false);
}
/**
* Checks if the given date is before the other.
*
* @param event1Date Event 1 date in format YYYY-MM-DD HH:MM
* @param event2Date Event 2 date in format YYYY-MM-DD HH:MM
* @return {boolean}
*/
export function isEventBefore(event1Date: string, event2Date: string): boolean {
const date1 = stringToDate(event1Date);
const date2 = stringToDate(event2Date);
if (date1 !== null && date2 !== null) return date1 < date2;
return false;
}
/**
* Gets only the date part of the given event date string in the format
* YYYY-MM-DD HH:MM
*
* @param dateString The string to get the date from
* @return {string|null} Date in format YYYY:MM:DD or null if given string is invalid
*/
export function getDateOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[0];
return null;
}
/**
* Gets only the time part of the given event date string in the format
* YYYY-MM-DD HH:MM
*
* @param dateString The string to get the date from
* @return {string|null} Time in format HH:MM or null if given string is invalid
*/
export function getTimeOnlyString(dateString: string): string | null {
if (isEventDateStringFormatValid(dateString)) return dateString.split(' ')[1];
return null;
}
/**
@ -144,26 +134,34 @@ export function dateToString(date: Date, isUTC: boolean): string {
* @return {string} Formatted string or "/ - /" on error
*/
export function getFormattedEventTime(start: string, end: string): string {
let formattedStr = '/ - /';
let startDate = stringToDate(start);
let endDate = stringToDate(end);
let formattedStr = '/ - /';
const startDate = stringToDate(start);
const endDate = stringToDate(end);
if (startDate !== null && endDate !== null && startDate.getTime() !== endDate.getTime()) {
formattedStr = String(startDate.getHours()).padStart(2, '0') + ':'
+ String(startDate.getMinutes()).padStart(2, '0') + ' - ';
if (endDate.getFullYear() > startDate.getFullYear()
|| endDate.getMonth() > startDate.getMonth()
|| endDate.getDate() > startDate.getDate())
formattedStr += '23:59';
else
formattedStr += String(endDate.getHours()).padStart(2, '0') + ':'
+ String(endDate.getMinutes()).padStart(2, '0');
} else if (startDate !== null)
formattedStr =
String(startDate.getHours()).padStart(2, '0') + ':'
+ String(startDate.getMinutes()).padStart(2, '0');
if (
startDate !== null &&
endDate !== null &&
startDate.getTime() !== endDate.getTime()
) {
formattedStr = `${String(startDate.getHours()).padStart(2, '0')}:${String(
startDate.getMinutes(),
).padStart(2, '0')} - `;
if (
endDate.getFullYear() > startDate.getFullYear() ||
endDate.getMonth() > startDate.getMonth() ||
endDate.getDate() > startDate.getDate()
)
formattedStr += '23:59';
else
formattedStr += `${String(endDate.getHours()).padStart(2, '0')}:${String(
endDate.getMinutes(),
).padStart(2, '0')}`;
} else if (startDate !== null)
formattedStr = `${String(startDate.getHours()).padStart(2, '0')}:${String(
startDate.getMinutes(),
).padStart(2, '0')}`;
return formattedStr
return formattedStr;
}
/**
@ -176,13 +174,19 @@ export function getFormattedEventTime(start: string, end: string): string {
* @return {boolean}
*/
export function isDescriptionEmpty(description: ?string): boolean {
if (description !== undefined && description !== null) {
return description
.split('<p>').join('') // Equivalent to a replace all
.split('</p>').join('')
.split('<br>').join('').trim() === '';
} else
return true;
if (description !== undefined && description !== null) {
return (
description
.split('<p>')
.join('') // Equivalent to a replace all
.split('</p>')
.join('')
.split('<br>')
.join('')
.trim() === ''
);
}
return true;
}
/**
@ -193,17 +197,43 @@ export function isDescriptionEmpty(description: ?string): boolean {
* @param numberOfMonths The number of months to create, starting from the current date
* @return {Object}
*/
export function generateEmptyCalendar(numberOfMonths: number): Object {
let end = new Date(Date.now());
end.setMonth(end.getMonth() + numberOfMonths);
let daysOfYear = {};
for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) {
const dateString = getDateOnlyString(
dateToString(new Date(d), false));
if (dateString !== null)
daysOfYear[dateString] = []
export function generateEmptyCalendar(
numberOfMonths: number,
): {[key: string]: Array<PlanningEventType>} {
const end = new Date(Date.now());
end.setMonth(end.getMonth() + numberOfMonths);
const daysOfYear = {};
for (let d = new Date(Date.now()); d <= end; d.setDate(d.getDate() + 1)) {
const dateString = getDateOnlyString(dateToString(new Date(d), false));
if (dateString !== null) daysOfYear[dateString] = [];
}
return daysOfYear;
}
/**
* Adds events to the given array depending on their starting date.
*
* Events starting before are added at the front.
*
* @param eventArray The array to hold sorted events
* @param event The event to add to the array
*/
export function pushEventInOrder(
eventArray: Array<PlanningEventType>,
event: PlanningEventType,
) {
if (eventArray.length === 0) eventArray.push(event);
else {
for (let i = 0; i < eventArray.length; i += 1) {
if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
eventArray.splice(i, 0, event);
break;
} else if (i === eventArray.length - 1) {
eventArray.push(event);
break;
}
}
return daysOfYear;
}
}
/**
@ -217,40 +247,17 @@ export function generateEmptyCalendar(numberOfMonths: number): Object {
* @param numberOfMonths The number of months to create the agenda for
* @return {Object}
*/
export function generateEventAgenda(eventList: Array<eventObject>, numberOfMonths: number): Object {
let agendaItems = generateEmptyCalendar(numberOfMonths);
for (let i = 0; i < eventList.length; i++) {
const dateString = getDateOnlyString(eventList[i].date_begin);
if (dateString !== null) {
const eventArray = agendaItems[dateString];
if (eventArray !== undefined)
pushEventInOrder(eventArray, eventList[i]);
}
}
return agendaItems;
}
/**
* Adds events to the given array depending on their starting date.
*
* Events starting before are added at the front.
*
* @param eventArray The array to hold sorted events
* @param event The event to add to the array
*/
export function pushEventInOrder(eventArray: Array<eventObject>, event: eventObject): Object {
if (eventArray.length === 0)
eventArray.push(event);
else {
for (let i = 0; i < eventArray.length; i++) {
if (isEventBefore(event.date_begin, eventArray[i].date_begin)) {
eventArray.splice(i, 0, event);
break;
} else if (i === eventArray.length - 1) {
eventArray.push(event);
break;
}
}
export function generateEventAgenda(
eventList: Array<PlanningEventType>,
numberOfMonths: number,
): {[key: string]: Array<PlanningEventType>} {
const agendaItems = generateEmptyCalendar(numberOfMonths);
for (let i = 0; i < eventList.length; i += 1) {
const dateString = getDateOnlyString(eventList[i].date_begin);
if (dateString != null) {
const eventArray = agendaItems[dateString];
if (eventArray != null) pushEventInOrder(eventArray, eventList[i]);
}
}
return agendaItems;
}

View file

@ -100,15 +100,17 @@ export async function apiRequest(
* If no data was found, returns an empty object
*
* @param url The urls to fetch data from
* @return Promise<{...}>
* @return Promise<any>
*/
export async function readData(url: string): Promise<{...}> {
return new Promise(
(resolve: (response: {...}) => void, reject: () => void) => {
fetch(url)
.then(async (response: Response): Promise<{...}> => response.json())
.then((data: {...}): void => resolve(data))
.catch((): void => reject());
},
);
// eslint-disable-next-line flowtype/no-weak-types
export async function readData(url: string): Promise<any> {
// eslint-disable-next-line flowtype/no-weak-types
return new Promise((resolve: (response: any) => void, reject: () => void) => {
fetch(url)
// eslint-disable-next-line flowtype/no-weak-types
.then(async (response: Response): Promise<any> => response.json())
// eslint-disable-next-line flowtype/no-weak-types
.then((data: any): void => resolve(data))
.catch((): void => reject());
});
}