skolplattformen-backup/apps/skolplattformen-app/components/absence.component.tsx

281 lines
9.3 KiB
TypeScript
Raw Normal View History

import { RouteProp, useRoute } from '@react-navigation/native'
import { NativeStackNavigationOptions } from '@react-navigation/native-stack'
2021-10-05 15:44:14 +00:00
import { useUser } from '@skolplattformen/hooks'
import {
Button,
CheckBox,
Input,
StyleService,
Text,
useStyleSheet,
} from '@ui-kitten/components'
2021-03-26 09:44:46 +00:00
import { Formik } from 'formik'
2021-02-20 08:38:08 +00:00
import moment from 'moment'
import Personnummer from 'personnummer'
import React, { useCallback } from 'react'
import { View } from 'react-native'
import DateTimePickerModal from 'react-native-modal-datetime-picker'
2021-02-20 08:38:08 +00:00
import * as Yup from 'yup'
2021-09-12 20:29:26 +00:00
import { defaultStackStyling } from '../design/navigationThemes'
2021-10-05 15:44:14 +00:00
import usePersonalStorage from '../hooks/usePersonalStorage'
import useSettingsStorage from '../hooks/useSettingsStorage'
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { studentName } from '../utils/peopleHelpers'
2021-02-20 08:38:08 +00:00
import { useSMS } from '../utils/SMS'
2021-04-13 15:03:14 +00:00
import { translate } from '../utils/translation'
import { AlertIcon } from './icon.component'
import { RootStackParamList } from './navigation.component'
import { NavigationTitle } from './navigationTitle.component'
type AbsenceRouteProps = RouteProp<RootStackParamList, 'Absence'>
interface AbsenceFormValues {
displayStartTimePicker: boolean
displayEndTimePicker: boolean
personalIdentityNumber: string
isFullDay: boolean
startTime: moment.Moment
endTime: moment.Moment
}
2021-09-12 20:29:26 +00:00
export const absenceRouteOptions =
(darkMode: boolean) =>
({
route,
}: {
route: RouteProp<RootStackParamList, 'Absence'>
}): NativeStackNavigationOptions => {
const child = route.params.child
return {
...defaultStackStyling(darkMode),
headerTitle: () => (
2021-09-12 20:29:26 +00:00
<NavigationTitle
title={translate('abscense.title')}
subtitle={studentName(child?.name)}
/>
),
}
}
const Absence = () => {
2021-04-18 23:03:16 +00:00
const AbsenceSchema = Yup.object().shape({
personalIdentityNumber: Yup.string()
2021-04-18 23:03:16 +00:00
.required(translate('abscense.personalNumberMissing'))
.test('is-valid', translate('abscense.invalidPersonalNumber'), (value) =>
value ? Personnummer.valid(value) : true
),
isFullDay: Yup.bool().required(),
})
const { data: user } = useUser()
const route = useRoute<AbsenceRouteProps>()
const { sendSMS } = useSMS()
const { child } = route.params
const [personalIdentityNumber, setPersonalIdentityNumber] = React.useState('')
const [personalIdsFromStorage, setPersonalIdInStorage] = useSettingsStorage(
'childPersonalIdentityNumber'
)
const personalIdKey = `@childPersonalIdNumber.${child.id}`
const minumumDate = moment().hours(8).minute(0)
const maximumDate = moment().hours(17).minute(0)
const styles = useStyleSheet(themedStyles)
const submit = useCallback(
async (values: AbsenceFormValues) => {
const personalIdNumber = Personnummer.parse(
values.personalIdentityNumber
).format()
if (values.isFullDay) {
sendSMS(personalIdNumber)
} else {
sendSMS(
`${personalIdNumber} ${moment(values.startTime).format(
'HHmm'
)}-${moment(values.endTime).format('HHmm')}`
)
}
const toStore = {
...personalIdsFromStorage,
...{ [personalIdKey]: personalIdNumber },
}
setPersonalIdInStorage(toStore)
},
[personalIdKey, personalIdsFromStorage, sendSMS, setPersonalIdInStorage]
)
React.useEffect(() => {
const personalIdFromStorage = personalIdsFromStorage[personalIdKey] || ''
setPersonalIdentityNumber(personalIdFromStorage || '')
}, [child, personalIdKey, personalIdsFromStorage, user])
const initialValues: AbsenceFormValues = {
displayStartTimePicker: false,
displayEndTimePicker: false,
personalIdentityNumber: personalIdentityNumber || '',
isFullDay: true,
startTime: moment().hours(Math.max(8, new Date().getHours())).minute(0),
endTime: maximumDate,
}
return (
<Formik
enableReinitialize
validationSchema={AbsenceSchema}
initialValues={initialValues}
onSubmit={submit}
>
{({
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
values,
touched,
errors,
}) => {
const hasError = (field: keyof typeof values) =>
errors[field] && touched[field]
return (
<View style={styles.wrap}>
<View style={styles.field}>
<Text style={styles.label}>
{translate('abscense.childsPersonalNumber')}
</Text>
<Input
testID="personalIdentityNumberInput"
keyboardType="number-pad"
onChangeText={handleChange('personalIdentityNumber')}
onBlur={handleBlur('personalIdentityNumber')}
status={hasError('personalIdentityNumber') ? 'danger' : 'basic'}
value={values.personalIdentityNumber}
style={styles.input}
2021-09-12 20:29:26 +00:00
placeholder="YYYYMMDD-XXXX"
accessoryRight={
hasError('personalIdentityNumber') ? AlertIcon : undefined
}
/>
{hasError('personalIdentityNumber') && (
<Text style={styles.error}>
{errors.personalIdentityNumber}
</Text>
)}
</View>
<View style={styles.field}>
<CheckBox
checked={values.isFullDay}
onChange={(checked) => setFieldValue('isFullDay', checked)}
>
{translate('abscense.entireDay')}
</CheckBox>
</View>
{!values.isFullDay && (
<View style={styles.partOfDay}>
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.startTime')}
</Text>
<Button
status="basic"
style={styles.pickerButton}
onPress={() =>
setFieldValue('displayStartTimePicker', true)
}
>
{moment(values.startTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.startTime).toDate()}
isVisible={values.displayStartTimePicker}
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('startTime', date)
setFieldValue('displayStartTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayStartTimePicker', false)
}
/>
</View>
<View style={styles.spacer} />
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.endTime')}
</Text>
<Button
status="basic"
style={styles.pickerButton}
onPress={() => setFieldValue('displayEndTimePicker', true)}
>
{moment(values.endTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.endTime).toDate()}
isVisible={values.displayEndTimePicker}
// Todo fix this
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('endTime', date)
setFieldValue('displayEndTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayEndTimePicker', false)
}
/>
</View>
</View>
)}
<Button onPress={() => handleSubmit()} status="primary">
{translate('general.send')}
</Button>
</View>
)
}}
</Formik>
)
}
export default Absence
const themedStyles = StyleService.create({
wrap: {
...LayoutStyle.flex.full,
padding: Sizing.t4,
2021-08-14 09:02:32 +00:00
backgroundColor: 'background-basic-color-2',
},
field: { marginBottom: Sizing.t4 },
partOfDay: { ...LayoutStyle.flex.row, marginBottom: Sizing.t4 },
spacer: { width: Sizing.t2 },
inputHalf: { ...LayoutStyle.flex.full },
input: {
backgroundColor: 'background-basic-color-1',
2021-09-12 20:29:26 +00:00
borderColor: 'color-input-border',
},
// TODO: Refactor to use mapping.json in eva design
pickerButton: {
backgroundColor: 'background-basic-color-1',
},
label: {
2021-09-12 20:29:26 +00:00
...Typography.fontSize.sm,
...Typography.fontWeight.bold,
2021-09-12 20:29:26 +00:00
marginBottom: Sizing.t2,
},
error: {
color: 'color-primary-600',
},
})