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:
parent
7bee08a550
commit
fce1d98847
|
@ -9,7 +9,7 @@
|
|||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {"no-console":"warn"}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
Text,
|
||||
useStyleSheet,
|
||||
} from '@ui-kitten/components'
|
||||
import moment from 'moment'
|
||||
import moment, { Moment } from 'moment'
|
||||
import React, { useEffect } from 'react'
|
||||
import { TouchableOpacity, useColorScheme, View } from 'react-native'
|
||||
import { useTranslation } from '../hooks/useTranslation'
|
||||
|
@ -31,6 +31,7 @@ interface ChildListItemProps {
|
|||
child: Child
|
||||
color: string
|
||||
updated: string
|
||||
currentDate?: Moment
|
||||
}
|
||||
type ChildListItemNavigationProp = StackNavigationProp<
|
||||
RootStackParamList,
|
||||
|
@ -41,6 +42,7 @@ export const ChildListItem = ({
|
|||
child,
|
||||
color,
|
||||
updated,
|
||||
currentDate = moment(),
|
||||
}: ChildListItemProps) => {
|
||||
// Forces rerender when child.id changes
|
||||
React.useEffect(() => {
|
||||
|
@ -57,16 +59,14 @@ export const ChildListItem = ({
|
|||
const { data: menu, reload: menuReload } = useMenu(child)
|
||||
const { data: schedule, reload: scheduleReload } = useSchedule(
|
||||
child,
|
||||
moment().toISOString(),
|
||||
moment().add(7, 'days').toISOString()
|
||||
moment(currentDate).toISOString(),
|
||||
moment(currentDate).add(7, 'days').toISOString()
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// Do not refresh if updated is empty (first render of component)
|
||||
if (updated === '') return
|
||||
|
||||
console.log('Reload', child.name, updated)
|
||||
|
||||
newsReload()
|
||||
classmatesReload()
|
||||
notificationsReload()
|
||||
|
@ -89,8 +89,8 @@ export const ChildListItem = ({
|
|||
)
|
||||
|
||||
const newsThisWeek = news.filter(({ modified, published }) => {
|
||||
const date = modified || published
|
||||
return date ? moment(date).isSame(moment(), 'week') : false
|
||||
const newsDate = modified || published
|
||||
return newsDate ? moment(newsDate).isSame(currentDate, 'week') : false
|
||||
})
|
||||
|
||||
const scheduleAndCalendarThisWeek = [
|
||||
|
@ -99,14 +99,18 @@ export const ChildListItem = ({
|
|||
].filter(({ startDate }) =>
|
||||
startDate
|
||||
? moment(startDate).isBetween(
|
||||
moment().startOf('day'),
|
||||
moment().add(7, 'days')
|
||||
moment(currentDate).startOf('day'),
|
||||
moment(currentDate).add(7, 'days')
|
||||
)
|
||||
: false
|
||||
)
|
||||
|
||||
const displayDate = (date: moment.MomentInput) => {
|
||||
return moment(date).fromNow()
|
||||
const displayDate = (inputDate: moment.MomentInput) => {
|
||||
return moment(inputDate).fromNow()
|
||||
}
|
||||
|
||||
const capitalizeFirstLetter = (string) => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
|
||||
const getClassName = () => {
|
||||
|
@ -168,12 +172,19 @@ export const ChildListItem = ({
|
|||
/>
|
||||
</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) => (
|
||||
<Text category="p1" key={i}>
|
||||
{`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
<Text category="c2" style={styles.label}>
|
||||
{t('navigation.news')}
|
||||
</Text>
|
||||
|
@ -182,11 +193,13 @@ export const ChildListItem = ({
|
|||
{notification.message}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
{newsThisWeek.slice(0, 3).map((newsItem, i) => (
|
||||
<Text category="p1" key={i}>
|
||||
{newsItem.header ?? ''}
|
||||
</Text>
|
||||
))}
|
||||
|
||||
{scheduleAndCalendarThisWeek.length ||
|
||||
notificationsThisWeek.length ||
|
||||
newsThisWeek.length ? null : (
|
||||
|
@ -194,14 +207,15 @@ export const ChildListItem = ({
|
|||
{t('news.noNewNewsItemsThisWeek')}
|
||||
</Text>
|
||||
)}
|
||||
{!menu[moment().isoWeekday() - 1] ? null : (
|
||||
{!menu[currentDate.isoWeekday() - 1] ? null : (
|
||||
<>
|
||||
<Text category="c2" style={styles.label}>
|
||||
{t('schedule.lunch')}
|
||||
</Text>
|
||||
<Text>{menu[moment().isoWeekday() - 1]?.description}</Text>
|
||||
<Text>{menu[currentDate.isoWeekday() - 1]?.description}</Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={styles.itemFooter}>
|
||||
<Button
|
||||
accessible
|
||||
|
@ -274,4 +288,8 @@ const themeStyles = StyleService.create({
|
|||
marginBottom: 0,
|
||||
},
|
||||
noNewNewsItemsText: {},
|
||||
weekday: {
|
||||
marginBottom: -5,
|
||||
padding: 0,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -26,6 +26,7 @@ import { Colors, Layout as LayoutStyle, Sizing, Typography } from '../styles'
|
|||
import { translate } from '../utils/translation'
|
||||
import { ChildListItem } from './childListItem.component'
|
||||
import { SettingsIcon, RefreshIcon } from './icon.component'
|
||||
import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
|
||||
|
||||
const colors = ['primary', 'success', 'info', 'warning', 'danger']
|
||||
|
||||
|
@ -80,80 +81,77 @@ export const Children = () => {
|
|||
})
|
||||
}, [navigation, reloadChildren])
|
||||
|
||||
const currentDate = getMeaningfulStartingDate()
|
||||
|
||||
// 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.
|
||||
return (
|
||||
<>
|
||||
{status === 'loaded' ? (
|
||||
<List
|
||||
contentContainerStyle={styles.childListContainer}
|
||||
data={childList}
|
||||
style={styles.childList}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyState}>
|
||||
<Text category="h2">{translate('children.noKids_title')}</Text>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
{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}>
|
||||
return status === 'loaded' ? (
|
||||
<List
|
||||
contentContainerStyle={styles.childListContainer}
|
||||
data={childList}
|
||||
style={styles.childList}
|
||||
ListEmptyComponent={
|
||||
<View style={styles.emptyState}>
|
||||
<Text category="h2">{translate('children.noKids_title')}</Text>
|
||||
<Text style={styles.emptyStateDescription}>
|
||||
{translate('children.noKids_description')}
|
||||
</Text>
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors={false}
|
||||
source={require('../assets/girls.png')}
|
||||
style={styles.loadingImage as ImageStyle}
|
||||
source={require('../assets/children.png')}
|
||||
style={styles.emptyStateImage as ImageStyle}
|
||||
/>
|
||||
{status === 'error' ? (
|
||||
<View style={styles.errorMessage}>
|
||||
<Text category="h5">
|
||||
{translate('children.loadingErrorHeading')}
|
||||
</Text>
|
||||
<Text style={{ fontSize: Sizing.t4 }}>
|
||||
{translate('children.loadingErrorInformationText')}
|
||||
</Text>
|
||||
<View style={styles.errorButtons}>
|
||||
<Button status="success" onPress={() => reloadChildren()}>
|
||||
{translate('children.tryAgain')}
|
||||
</Button>
|
||||
<Button
|
||||
status="basic"
|
||||
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>
|
||||
}
|
||||
renderItem={({ item: child, index }: ListRenderItemInfo<Child>) => (
|
||||
<ChildListItem
|
||||
child={child}
|
||||
color={colors[index % colors.length]}
|
||||
currentDate={currentDate}
|
||||
updated={updatedAt}
|
||||
key={child.id}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.loading}>
|
||||
<Image
|
||||
accessibilityIgnoresInvertColors={false}
|
||||
source={require('../assets/girls.png')}
|
||||
style={styles.loadingImage as ImageStyle}
|
||||
/>
|
||||
{status === 'error' ? (
|
||||
<View style={styles.errorMessage}>
|
||||
<Text category="h5">{translate('children.loadingErrorHeading')}</Text>
|
||||
<Text style={{ fontSize: Sizing.t4 }}>
|
||||
{translate('children.loadingErrorInformationText')}
|
||||
</Text>
|
||||
<View style={styles.errorButtons}>
|
||||
<Button status="success" onPress={() => reloadChildren()}>
|
||||
{translate('children.tryAgain')}
|
||||
</Button>
|
||||
<Button
|
||||
status="basic"
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Child } from '@skolplattformen/api'
|
|||
import { useTimetable } from '@skolplattformen/hooks'
|
||||
import { StyleService, Text, useStyleSheet } from '@ui-kitten/components'
|
||||
import moment, { Moment } from 'moment'
|
||||
import React from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { LanguageService } from '../services/languageService'
|
||||
import { translate } from '../utils/translation'
|
||||
|
@ -12,9 +12,13 @@ interface DaySummaryProps {
|
|||
date?: Moment
|
||||
}
|
||||
|
||||
export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => {
|
||||
export const DaySummary = ({
|
||||
child,
|
||||
date: currentDate = moment(),
|
||||
}: DaySummaryProps) => {
|
||||
const styles = useStyleSheet(themedStyles)
|
||||
const [year, week] = [moment().isoWeekYear(), moment().isoWeek()]
|
||||
const [week, year] = [currentDate.isoWeek(), currentDate.isoWeekYear()]
|
||||
|
||||
const { data: weekLessons } = useTimetable(
|
||||
child,
|
||||
week,
|
||||
|
@ -23,7 +27,7 @@ export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => {
|
|||
)
|
||||
|
||||
const lessons = weekLessons
|
||||
.filter((lesson) => lesson.dayOfWeek === date.isoWeekday())
|
||||
.filter((lesson) => lesson.dayOfWeek === currentDate.isoWeekday())
|
||||
.sort((a, b) => a.dateStart.localeCompare(b.dateStart))
|
||||
|
||||
if (lessons.length <= 0) {
|
||||
|
@ -53,15 +57,21 @@ export const DaySummary = ({ child, date = moment() }: DaySummaryProps) => {
|
|||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.part}>
|
||||
<View>
|
||||
<Text category="c2" style={styles.label}>
|
||||
|
||||
</Text>
|
||||
<Text category="s2">
|
||||
{gymBag
|
||||
? ` 🤼♀️ ${translate('schedule.gymBag', {
|
||||
defaultValue: 'Gympapåse',
|
||||
})}`
|
||||
: ''}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text category="s2">
|
||||
{gymBag
|
||||
? ` 🤼♀️ ${translate('schedule.gymBag', {
|
||||
defaultValue: 'Gympapåse',
|
||||
})}`
|
||||
: ''}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -76,4 +86,7 @@ const themedStyles = StyleService.create({
|
|||
label: {
|
||||
marginTop: 10,
|
||||
},
|
||||
heading: {
|
||||
marginBottom: -10,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -16,6 +16,7 @@ import { View } from 'react-native'
|
|||
import { LanguageService } from '../services/languageService'
|
||||
import { Sizing, Typography } from '../styles'
|
||||
import { TransitionView } from './transitionView.component'
|
||||
import { getMeaningfulStartingDate } from '../utils/calendarHelpers'
|
||||
|
||||
interface WeekProps {
|
||||
child: Child
|
||||
|
@ -107,10 +108,12 @@ export const Day = ({ weekDay, lunch, lessons }: DayProps) => {
|
|||
export const Week = ({ child }: WeekProps) => {
|
||||
moment.locale(LanguageService.getLocale())
|
||||
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 [showSchema, setShowSchema] = useState(false)
|
||||
const [year, week] = [moment().isoWeekYear(), moment().isoWeek()]
|
||||
const [year, week] = [displayDate.isoWeekYear(), displayDate.isoWeek()]
|
||||
const { data: lessons } = useTimetable(
|
||||
child,
|
||||
week,
|
||||
|
|
|
@ -80,7 +80,8 @@
|
|||
"send": "Send",
|
||||
"settings": "Settings",
|
||||
"socialSecurityNumber": "Personal identity number",
|
||||
"title": "Öppna skolplattformen"
|
||||
"title": "Öppna skolplattformen",
|
||||
"tomorrow": "Tomorrow"
|
||||
},
|
||||
"language": {
|
||||
"changeLanguage": "Change language",
|
||||
|
|
|
@ -82,7 +82,8 @@
|
|||
"title": "Öppna skolplattformen",
|
||||
"cancel": "Avbryt",
|
||||
"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": {
|
||||
"changeLanguage": "Byt språk",
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -84,6 +84,11 @@ export const timetable = (
|
|||
if (response.error) {
|
||||
throw new Error(response.error)
|
||||
}
|
||||
|
||||
if(!response.data.lessonInfo){
|
||||
throw new Error("Empty lessonInfo received")
|
||||
}
|
||||
|
||||
return response.data.lessonInfo.map((entry) =>
|
||||
timetableEntry(entry, year, week, lang)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue