254 lines
5.6 KiB
TypeScript
254 lines
5.6 KiB
TypeScript
/* eslint-disable react-hooks/rules-of-hooks */
|
|
import {
|
|
Api,
|
|
CalendarItem,
|
|
Child,
|
|
Classmate,
|
|
EtjanstChild,
|
|
MenuItem,
|
|
NewsItem,
|
|
Notification,
|
|
ScheduleItem,
|
|
Skola24Child,
|
|
TimetableEntry,
|
|
User,
|
|
} from '@skolplattformen/api-skolplattformen'
|
|
import { Language } from '@skolplattformen/curriculum'
|
|
import { DateTime } from 'luxon'
|
|
import { useEffect, useState } from 'react'
|
|
import { useDispatch } from 'react-redux'
|
|
import { loadAction } from './actions'
|
|
import { merge } from './childlists'
|
|
import { useApi } from './context'
|
|
import store from './store'
|
|
import {
|
|
ApiCall,
|
|
EntityHookResult,
|
|
EntityMap,
|
|
EntityName,
|
|
EntityStoreRootState,
|
|
ExtraActionProps,
|
|
} from './types'
|
|
|
|
interface StoreSelector<T> {
|
|
(state: EntityStoreRootState): EntityMap<T>
|
|
}
|
|
|
|
const hook = <T>(
|
|
entityName: EntityName,
|
|
key: string,
|
|
defaultValue: T,
|
|
selector: StoreSelector<T>,
|
|
apiCaller: (api: Api) => ApiCall<T>
|
|
): EntityHookResult<T> => {
|
|
const { api, isLoggedIn, reporter, storage } = useApi()
|
|
|
|
const getState = (): EntityStoreRootState =>
|
|
store.getState() as unknown as EntityStoreRootState
|
|
const select = (storeState: EntityStoreRootState) => {
|
|
const stateMap = selector(storeState) || {}
|
|
const state = stateMap[key] || { status: 'pending', data: defaultValue }
|
|
return state
|
|
}
|
|
const initialState = select(getState())
|
|
const [state, setState] = useState(initialState)
|
|
const dispatch = useDispatch()
|
|
|
|
const load = (force = false) => {
|
|
if (
|
|
isLoggedIn &&
|
|
state.status !== 'loading' &&
|
|
((force && !api.isFake) || state.status === 'pending')
|
|
) {
|
|
const extra: ExtraActionProps<T> = {
|
|
key,
|
|
defaultValue,
|
|
apiCall: apiCaller(api),
|
|
retries: 0,
|
|
}
|
|
|
|
// Only use cache when not in fake mode
|
|
if (!api.isFake) {
|
|
const pnr = api.getPersonalNumber()
|
|
|
|
// Only get from cache first time
|
|
if (state.status === 'pending') {
|
|
extra.getFromCache = () => storage.getItem(`${pnr}_${key}`)
|
|
}
|
|
extra.saveToCache = (value: string) =>
|
|
storage.setItem(`${pnr}_${key}`, value)
|
|
}
|
|
const action = loadAction<T>(entityName, extra)
|
|
dispatch(action)
|
|
}
|
|
}
|
|
useEffect(() => {
|
|
load()
|
|
}, [isLoggedIn])
|
|
|
|
let mounted: boolean
|
|
useEffect(() => {
|
|
mounted = true
|
|
return () => {
|
|
mounted = false
|
|
}
|
|
}, [])
|
|
|
|
const listener = () => {
|
|
if (!mounted) return
|
|
|
|
const newState = select(getState())
|
|
if (
|
|
newState.status !== state.status ||
|
|
newState.data !== state.data ||
|
|
newState.error !== state.error
|
|
) {
|
|
setState(newState)
|
|
|
|
if (newState.error) {
|
|
const description = `Error getting ${entityName} from API`
|
|
reporter.error(newState.error, description)
|
|
}
|
|
}
|
|
}
|
|
useEffect(() => store.subscribe(listener), [])
|
|
|
|
return {
|
|
...state,
|
|
reload: () => load(true),
|
|
}
|
|
}
|
|
|
|
export const useEtjanstChildren = () =>
|
|
hook<EtjanstChild[]>(
|
|
'ETJANST_CHILDREN',
|
|
'etjanst_children',
|
|
[],
|
|
(s) => s.etjanstChildren,
|
|
(api) => () => api.getChildren()
|
|
)
|
|
|
|
export const useSkola24Children = () =>
|
|
hook<Skola24Child[]>(
|
|
'SKOLA24_CHILDREN',
|
|
'skola24_children',
|
|
[],
|
|
(s) => s.skola24Children,
|
|
(api) => () => api.getSkola24Children()
|
|
)
|
|
|
|
export const useCalendar = (child: Child) =>
|
|
hook<CalendarItem[]>(
|
|
'CALENDAR',
|
|
`calendar_${child.id}`,
|
|
[],
|
|
(s) => s.calendar,
|
|
(api) => () => api.getCalendar(child)
|
|
)
|
|
|
|
export const useClassmates = (child: Child) =>
|
|
hook<Classmate[]>(
|
|
'CLASSMATES',
|
|
`classmates_${child.id}`,
|
|
[],
|
|
(s) => s.classmates,
|
|
(api) => () => api.getClassmates(child)
|
|
)
|
|
|
|
export const useMenu = (child: Child) =>
|
|
hook<MenuItem[]>(
|
|
'MENU',
|
|
`menu_${child.id}`,
|
|
[],
|
|
(s) => s.menu,
|
|
(api) => () => api.getMenu(child)
|
|
)
|
|
|
|
export const useNews = (child: Child) =>
|
|
hook<NewsItem[]>(
|
|
'NEWS',
|
|
`news_${child.id}`,
|
|
[],
|
|
(s) => s.news,
|
|
(api) => () => api.getNews(child)
|
|
)
|
|
|
|
export const useNewsDetails = (child: Child, news: NewsItem) =>
|
|
hook<NewsItem>(
|
|
'NEWS_DETAILS',
|
|
`news_details_${news.id}`,
|
|
news,
|
|
(s) => s.newsDetails,
|
|
(api) => () => api.getNewsDetails(child, news)
|
|
)
|
|
|
|
export const useNotifications = (child: Child) =>
|
|
hook<Notification[]>(
|
|
'NOTIFICATIONS',
|
|
`notifications_${child.id}`,
|
|
[],
|
|
(s) => s.notifications,
|
|
(api) => () => api.getNotifications(child)
|
|
)
|
|
|
|
export const useSchedule = (child: Child, from: string, to: string) =>
|
|
hook<ScheduleItem[]>(
|
|
'SCHEDULE',
|
|
`schedule_${child.id}_${from}_${to}`,
|
|
[],
|
|
(s) => s.schedule,
|
|
(api) => () =>
|
|
api.getSchedule(child, DateTime.fromISO(from), DateTime.fromISO(to))
|
|
)
|
|
|
|
export const useTimetable = (
|
|
child: Skola24Child,
|
|
week: number,
|
|
year: number,
|
|
lang: Language
|
|
) =>
|
|
hook<TimetableEntry[]>(
|
|
'TIMETABLE',
|
|
`timetable_${child.personGuid}_${week}_${year}_${lang}`,
|
|
[],
|
|
(s) => s.timetable,
|
|
(api) => () => api.getTimetable(child, week, year, lang)
|
|
)
|
|
|
|
export const useUser = () =>
|
|
hook<User>(
|
|
'USER',
|
|
'user',
|
|
{},
|
|
(s) => s.user,
|
|
(api) => () => api.getUser()
|
|
)
|
|
|
|
export const useChildList = (): EntityHookResult<Child[]> => {
|
|
const {
|
|
data: etjanstData,
|
|
status,
|
|
error,
|
|
reload: etjanstReload,
|
|
} = useEtjanstChildren()
|
|
const { data: skola24Data, reload: skola24Reload } = useSkola24Children()
|
|
|
|
const [data, setData] = useState<Child[]>([])
|
|
const reload = () => {
|
|
etjanstReload()
|
|
skola24Reload()
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!etjanstData.length) return
|
|
setData(merge(etjanstData, skola24Data))
|
|
}, [etjanstData, skola24Data])
|
|
|
|
return {
|
|
data,
|
|
status,
|
|
error,
|
|
reload,
|
|
}
|
|
}
|