feat: 🎸 Skip to the next day in calendar (#425)

* Add meaningful starting date to components
* Add warn on console use
* Add better error message if response from skola24 is wrong
* Change heading to only show name of day
* Remove extra menu

Co-authored-by: Andreas Eriksson <addeman@gmail.com>
This commit is contained in:
Christian Landgren 2021-12-03 11:27:09 +01:00 committed by GitHub
parent 7bee08a550
commit fce1d98847
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 98 deletions

View File

@ -9,7 +9,7 @@
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {} "rules": {"no-console":"warn"}
}, },
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],

View File

@ -16,7 +16,7 @@ import {
Text, Text,
useStyleSheet, useStyleSheet,
} from '@ui-kitten/components' } from '@ui-kitten/components'
import moment from 'moment' import moment, { Moment } from 'moment'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { TouchableOpacity, useColorScheme, View } from 'react-native' import { TouchableOpacity, useColorScheme, View } from 'react-native'
import { useTranslation } from '../hooks/useTranslation' import { useTranslation } from '../hooks/useTranslation'
@ -31,6 +31,7 @@ interface ChildListItemProps {
child: Child child: Child
color: string color: string
updated: string updated: string
currentDate?: Moment
} }
type ChildListItemNavigationProp = StackNavigationProp< type ChildListItemNavigationProp = StackNavigationProp<
RootStackParamList, RootStackParamList,
@ -41,6 +42,7 @@ export const ChildListItem = ({
child, child,
color, color,
updated, updated,
currentDate = moment(),
}: ChildListItemProps) => { }: ChildListItemProps) => {
// Forces rerender when child.id changes // Forces rerender when child.id changes
React.useEffect(() => { React.useEffect(() => {
@ -57,16 +59,14 @@ export const ChildListItem = ({
const { data: menu, reload: menuReload } = useMenu(child) const { data: menu, reload: menuReload } = useMenu(child)
const { data: schedule, reload: scheduleReload } = useSchedule( const { data: schedule, reload: scheduleReload } = useSchedule(
child, child,
moment().toISOString(), moment(currentDate).toISOString(),
moment().add(7, 'days').toISOString() moment(currentDate).add(7, 'days').toISOString()
) )
useEffect(() => { useEffect(() => {
// Do not refresh if updated is empty (first render of component) // Do not refresh if updated is empty (first render of component)
if (updated === '') return if (updated === '') return
console.log('Reload', child.name, updated)
newsReload() newsReload()
classmatesReload() classmatesReload()
notificationsReload() notificationsReload()
@ -89,8 +89,8 @@ export const ChildListItem = ({
) )
const newsThisWeek = news.filter(({ modified, published }) => { const newsThisWeek = news.filter(({ modified, published }) => {
const date = modified || published const newsDate = modified || published
return date ? moment(date).isSame(moment(), 'week') : false return newsDate ? moment(newsDate).isSame(currentDate, 'week') : false
}) })
const scheduleAndCalendarThisWeek = [ const scheduleAndCalendarThisWeek = [
@ -99,14 +99,18 @@ export const ChildListItem = ({
].filter(({ startDate }) => ].filter(({ startDate }) =>
startDate startDate
? moment(startDate).isBetween( ? moment(startDate).isBetween(
moment().startOf('day'), moment(currentDate).startOf('day'),
moment().add(7, 'days') moment(currentDate).add(7, 'days')
) )
: false : false
) )
const displayDate = (date: moment.MomentInput) => { const displayDate = (inputDate: moment.MomentInput) => {
return moment(date).fromNow() return moment(inputDate).fromNow()
}
const capitalizeFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1)
} }
const getClassName = () => { const getClassName = () => {
@ -168,12 +172,19 @@ export const ChildListItem = ({
/> />
</View> </View>
</View> </View>
<DaySummary child={child} /> {currentDate.hour() > 17 && currentDate.hour() <= 23 ? (
<Text category="c2" style={styles.weekday}>
{capitalizeFirstLetter(currentDate.format('dddd'))}
</Text>
) : null}
<DaySummary child={child} date={currentDate} />
{scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => ( {scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => (
<Text category="p1" key={i}> <Text category="p1" key={i}>
{`${calendarItem.title} (${displayDate(calendarItem.startDate)})`} {`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
</Text> </Text>
))} ))}
<Text category="c2" style={styles.label}> <Text category="c2" style={styles.label}>
{t('navigation.news')} {t('navigation.news')}
</Text> </Text>
@ -182,11 +193,13 @@ export const ChildListItem = ({
{notification.message} {notification.message}
</Text> </Text>
))} ))}
{newsThisWeek.slice(0, 3).map((newsItem, i) => ( {newsThisWeek.slice(0, 3).map((newsItem, i) => (
<Text category="p1" key={i}> <Text category="p1" key={i}>
{newsItem.header ?? ''} {newsItem.header ?? ''}
</Text> </Text>
))} ))}
{scheduleAndCalendarThisWeek.length || {scheduleAndCalendarThisWeek.length ||
notificationsThisWeek.length || notificationsThisWeek.length ||
newsThisWeek.length ? null : ( newsThisWeek.length ? null : (
@ -194,14 +207,15 @@ export const ChildListItem = ({
{t('news.noNewNewsItemsThisWeek')} {t('news.noNewNewsItemsThisWeek')}
</Text> </Text>
)} )}
{!menu[moment().isoWeekday() - 1] ? null : ( {!menu[currentDate.isoWeekday() - 1] ? null : (
<> <>
<Text category="c2" style={styles.label}> <Text category="c2" style={styles.label}>
{t('schedule.lunch')} {t('schedule.lunch')}
</Text> </Text>
<Text>{menu[moment().isoWeekday() - 1]?.description}</Text> <Text>{menu[currentDate.isoWeekday() - 1]?.description}</Text>
</> </>
)} )}
<View style={styles.itemFooter}> <View style={styles.itemFooter}>
<Button <Button
accessible accessible
@ -274,4 +288,8 @@ const themeStyles = StyleService.create({
marginBottom: 0, marginBottom: 0,
}, },
noNewNewsItemsText: {}, noNewNewsItemsText: {},
weekday: {
marginBottom: -5,
padding: 0,
},
}) })

View File

@ -26,6 +26,7 @@ import { Colors, Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { translate } from '../utils/translation' import { translate } from '../utils/translation'
import { ChildListItem } from './childListItem.component' import { ChildListItem } from './childListItem.component'
import { SettingsIcon, RefreshIcon } from './icon.component' import { SettingsIcon, RefreshIcon } from './icon.component'
import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
const colors = ['primary', 'success', 'info', 'warning', 'danger'] const colors = ['primary', 'success', 'info', 'warning', 'danger']
@ -80,80 +81,77 @@ export const Children = () => {
}) })
}, [navigation, reloadChildren]) }, [navigation, reloadChildren])
const currentDate = getMeaningfulStartingDate()
// We need to skip safe area view here, due to the reason that it's adding a white border // We need to skip safe area view here, due to the reason that it's adding a white border
// when this view is actually lightgrey. Taking the padding top value from the use inset hook. // when this view is actually lightgrey. Taking the padding top value from the use inset hook.
return ( return status === 'loaded' ? (
<> <List
{status === 'loaded' ? ( contentContainerStyle={styles.childListContainer}
<List data={childList}
contentContainerStyle={styles.childListContainer} style={styles.childList}
data={childList} ListEmptyComponent={
style={styles.childList} <View style={styles.emptyState}>
ListEmptyComponent={ <Text category="h2">{translate('children.noKids_title')}</Text>
<View style={styles.emptyState}> <Text style={styles.emptyStateDescription}>
<Text category="h2">{translate('children.noKids_title')}</Text> {translate('children.noKids_description')}
<Text style={styles.emptyStateDescription}> </Text>
{translate('children.noKids_description')}
</Text>
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/children.png')}
style={styles.emptyStateImage as ImageStyle}
/>
</View>
}
renderItem={({ item: child, index }: ListRenderItemInfo<Child>) => (
<ChildListItem
child={child}
color={colors[index % colors.length]}
key={child.id}
updated={updatedAt}
/>
)}
/>
) : (
<View style={styles.loading}>
<Image <Image
accessibilityIgnoresInvertColors={false} accessibilityIgnoresInvertColors={false}
source={require('../assets/girls.png')} source={require('../assets/children.png')}
style={styles.loadingImage as ImageStyle} style={styles.emptyStateImage as ImageStyle}
/> />
{status === 'error' ? ( </View>
<View style={styles.errorMessage}> }
<Text category="h5"> renderItem={({ item: child, index }: ListRenderItemInfo<Child>) => (
{translate('children.loadingErrorHeading')} <ChildListItem
</Text> child={child}
<Text style={{ fontSize: Sizing.t4 }}> color={colors[index % colors.length]}
{translate('children.loadingErrorInformationText')} currentDate={currentDate}
</Text> updated={updatedAt}
<View style={styles.errorButtons}> key={child.id}
<Button status="success" onPress={() => reloadChildren()}> />
{translate('children.tryAgain')} )}
</Button> />
<Button ) : (
status="basic" <View style={styles.loading}>
onPress={() => <Image
Linking.openURL('https://skolplattformen.org/status') accessibilityIgnoresInvertColors={false}
} source={require('../assets/girls.png')}
> style={styles.loadingImage as ImageStyle}
{translate('children.viewStatus')} />
</Button> {status === 'error' ? (
<Button onPress={() => logout()}> <View style={styles.errorMessage}>
{translate('general.logout')} <Text category="h5">{translate('children.loadingErrorHeading')}</Text>
</Button> <Text style={{ fontSize: Sizing.t4 }}>
</View> {translate('children.loadingErrorInformationText')}
</View> </Text>
) : ( <View style={styles.errorButtons}>
<View style={styles.loadingMessage}> <Button status="success" onPress={() => reloadChildren()}>
<Spinner size="large" status="primary" /> {translate('children.tryAgain')}
<Text category="h1" style={styles.loadingText}> </Button>
{translate('general.loading')} <Button
</Text> status="basic"
</View> onPress={() =>
)} Linking.openURL('https://skolplattformen.org/status')
}
>
{translate('children.viewStatus')}
</Button>
<Button onPress={() => logout()}>
{translate('general.logout')}
</Button>
</View>
</View>
) : (
<View style={styles.loadingMessage}>
<Spinner size="large" status="primary" />
<Text category="h1" style={styles.loadingText}>
{translate('general.loading')}
</Text>
</View> </View>
)} )}
</> </View>
) )
} }

View File

@ -2,7 +2,7 @@ import { Child } from '@skolplattformen/api'
import { useTimetable } from '@skolplattformen/hooks' import { useTimetable } from '@skolplattformen/hooks'
import { StyleService, Text, useStyleSheet } from '@ui-kitten/components' import { StyleService, Text, useStyleSheet } from '@ui-kitten/components'
import moment, { Moment } from 'moment' import moment, { Moment } from 'moment'
import React from 'react' import React, { useCallback, useEffect, useState } from 'react'
import { View } from 'react-native' import { View } from 'react-native'
import { LanguageService } from '../services/languageService' import { LanguageService } from '../services/languageService'
import { translate } from '../utils/translation' import { translate } from '../utils/translation'
@ -12,9 +12,13 @@ interface DaySummaryProps {
date?: Moment date?: Moment
} }
export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => { export const DaySummary = ({
child,
date: currentDate = moment(),
}: DaySummaryProps) => {
const styles = useStyleSheet(themedStyles) const styles = useStyleSheet(themedStyles)
const [year, week] = [moment().isoWeekYear(), moment().isoWeek()] const [week, year] = [currentDate.isoWeek(), currentDate.isoWeekYear()]
const { data: weekLessons } = useTimetable( const { data: weekLessons } = useTimetable(
child, child,
week, week,
@ -23,7 +27,7 @@ export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => {
) )
const lessons = weekLessons const lessons = weekLessons
.filter((lesson) => lesson.dayOfWeek === date.isoWeekday()) .filter((lesson) => lesson.dayOfWeek === currentDate.isoWeekday())
.sort((a, b) => a.dateStart.localeCompare(b.dateStart)) .sort((a, b) => a.dateStart.localeCompare(b.dateStart))
if (lessons.length <= 0) { if (lessons.length <= 0) {
@ -53,15 +57,21 @@ export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => {
</Text> </Text>
</View> </View>
</View> </View>
<View style={styles.part}>
<View>
<Text category="c2" style={styles.label}>
&nbsp;
</Text>
<Text category="s2">
{gymBag
? ` 🤼‍♀️ ${translate('schedule.gymBag', {
defaultValue: 'Gympapåse',
})}`
: ''}
</Text>
</View>
</View>
</View> </View>
<Text category="s2">
{gymBag
? ` 🤼‍♀️ ${translate('schedule.gymBag', {
defaultValue: 'Gympapåse',
})}`
: ''}
</Text>
</View> </View>
) )
} }
@ -76,4 +86,7 @@ const themedStyles = StyleService.create({
label: { label: {
marginTop: 10, marginTop: 10,
}, },
heading: {
marginBottom: -10,
},
}) })

View File

@ -16,6 +16,7 @@ import { View } from 'react-native'
import { LanguageService } from '../services/languageService' import { LanguageService } from '../services/languageService'
import { Sizing, Typography } from '../styles' import { Sizing, Typography } from '../styles'
import { TransitionView } from './transitionView.component' import { TransitionView } from './transitionView.component'
import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
interface WeekProps { interface WeekProps {
child: Child child: Child
@ -107,10 +108,12 @@ export const Day = ({ weekDay, lunch, lessons }: DayProps) => {
export const Week = ({ child }: WeekProps) => { export const Week = ({ child }: WeekProps) => {
moment.locale(LanguageService.getLocale()) moment.locale(LanguageService.getLocale())
const days = moment.weekdaysShort().slice(1, 6) const days = moment.weekdaysShort().slice(1, 6)
const currentDayIndex = Math.min(moment().isoWeekday() - 1, 5) const displayDate = getMeaningfulStartingDate()
const currentDayIndex = Math.min(moment(displayDate).isoWeekday() - 1, 5)
const [selectedIndex, setSelectedIndex] = useState(currentDayIndex) const [selectedIndex, setSelectedIndex] = useState(currentDayIndex)
const [showSchema, setShowSchema] = useState(false) const [showSchema, setShowSchema] = useState(false)
const [year, week] = [moment().isoWeekYear(), moment().isoWeek()] const [year, week] = [displayDate.isoWeekYear(), displayDate.isoWeek()]
const { data: lessons } = useTimetable( const { data: lessons } = useTimetable(
child, child,
week, week,

View File

@ -80,7 +80,8 @@
"send": "Send", "send": "Send",
"settings": "Settings", "settings": "Settings",
"socialSecurityNumber": "Personal identity number", "socialSecurityNumber": "Personal identity number",
"title": "Öppna skolplattformen" "title": "Öppna skolplattformen",
"tomorrow": "Tomorrow"
}, },
"language": { "language": {
"changeLanguage": "Change language", "changeLanguage": "Change language",

View File

@ -82,7 +82,8 @@
"title": "Öppna skolplattformen", "title": "Öppna skolplattformen",
"cancel": "Avbryt", "cancel": "Avbryt",
"logoutAndClearAllDataInclSettings": "Logga ut och rensa all sparad data inkl inställningar", "logoutAndClearAllDataInclSettings": "Logga ut och rensa all sparad data inkl inställningar",
"logoutAndClearPersonalData": "Logga ut och rensa all personlig data" "logoutAndClearPersonalData": "Logga ut och rensa all personlig data",
"tomorrow": "Imorgon"
}, },
"language": { "language": {
"changeLanguage": "Byt språk", "changeLanguage": "Byt språk",

View File

@ -0,0 +1,9 @@
import moment from 'moment'
export const getMeaningfulStartingDate = (date = moment()) => {
// are we on the evening?
if (date.hour() > 17) date = date.add('1', 'day')
// are we on the weekend
if (date.isoWeekday() > 5) date = date.add(5, 'days').startOf('isoWeek')
return date
}

View File

@ -84,6 +84,11 @@ export const timetable = (
if (response.error) { if (response.error) {
throw new Error(response.error) throw new Error(response.error)
} }
if(!response.data.lessonInfo){
throw new Error("Empty lessonInfo received")
}
return response.data.lessonInfo.map((entry) => return response.data.lessonInfo.map((entry) =>
timetableEntry(entry, year, week, lang) timetableEntry(entry, year, week, lang)
) )