-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
558 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import FormList from '@/components/Elements/Universal/FormList' | ||
import { type Colors } from '@/stores/colors' | ||
import { type FormListSections } from '@/stores/types/components' | ||
import { type NormalizedLecturer } from '@/utils/lecturers-utils' | ||
import { useTheme } from '@react-navigation/native' | ||
import { router, useLocalSearchParams } from 'expo-router' | ||
import { StatusBar } from 'expo-status-bar' | ||
import React from 'react' | ||
import { Linking, ScrollView, StyleSheet, Text, View } from 'react-native' | ||
|
||
export default function LecturerDetail(): JSX.Element { | ||
const colors = useTheme().colors as Colors | ||
const { lecturerEntry } = useLocalSearchParams<{ lecturerEntry: string }>() | ||
const lecturer: NormalizedLecturer | undefined = | ||
lecturerEntry != null ? JSON.parse(lecturerEntry) : undefined | ||
|
||
const sections: FormListSections[] = [ | ||
{ | ||
header: 'Details', | ||
items: [ | ||
{ | ||
title: 'Name', | ||
value: `${lecturer?.vorname ?? ''} ${lecturer?.name ?? ''}`, | ||
disabled: true, | ||
}, | ||
{ | ||
title: 'Title', | ||
value: lecturer?.titel, | ||
disabled: true, | ||
}, | ||
{ | ||
title: 'Organization', | ||
value: lecturer?.organisation, | ||
disabled: true, | ||
}, | ||
|
||
{ | ||
title: 'Function', | ||
value: lecturer?.funktion, | ||
disabled: true, | ||
}, | ||
], | ||
}, | ||
{ | ||
header: 'Contact', | ||
items: [ | ||
{ | ||
title: 'Room', | ||
value: lecturer?.room_short, | ||
disabled: lecturer?.room_short == null, | ||
iconColor: colors.primary, | ||
onPress: () => { | ||
router.push('(tabs)/map') | ||
router.setParams({ | ||
q: lecturer?.room_short ?? '', | ||
h: 'true', | ||
}) | ||
}, | ||
}, | ||
{ | ||
title: 'E-Mail', | ||
value: lecturer?.email, | ||
disabled: lecturer?.email == null, | ||
iconColor: colors.primary, | ||
onPress: () => { | ||
void Linking.openURL(`mailto:${lecturer?.email ?? ''}`) | ||
}, | ||
}, | ||
{ | ||
title: 'Phone', | ||
value: lecturer?.tel_dienst, | ||
disabled: lecturer?.tel_dienst == null, | ||
iconColor: colors.primary, | ||
onPress: () => { | ||
void Linking.openURL( | ||
`tel:${ | ||
lecturer?.tel_dienst?.replace(/\s+/g, '') ?? '' | ||
}` | ||
) | ||
}, | ||
}, | ||
{ | ||
title: 'Office Hours', | ||
value: lecturer?.sprechstunde, | ||
disabled: true, | ||
}, | ||
{ | ||
title: 'Exam Insigths', | ||
value: lecturer?.einsichtnahme, | ||
disabled: true, | ||
}, | ||
], | ||
}, | ||
] | ||
|
||
return ( | ||
<ScrollView> | ||
<StatusBar style="light" animated={true} hidden={false} /> | ||
<View | ||
style={[ | ||
styles.titleContainer, | ||
{ backgroundColor: colors.card }, | ||
]} | ||
> | ||
<Text | ||
style={[styles.titleText, { color: colors.text }]} | ||
allowFontScaling={true} | ||
adjustsFontSizeToFit={true} | ||
numberOfLines={2} | ||
> | ||
{`${[lecturer?.titel, lecturer?.vorname, lecturer?.name] | ||
.join(' ') | ||
.trim()}`} | ||
</Text> | ||
</View> | ||
<FormList sections={sections} /> | ||
</ScrollView> | ||
) | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
titleContainer: { | ||
alignSelf: 'center', | ||
width: '92%', | ||
marginTop: 20, | ||
paddingHorizontal: 5, | ||
paddingVertical: 10, | ||
borderRadius: 8, | ||
alignItems: 'center', | ||
}, | ||
titleText: { | ||
fontSize: 18, | ||
textAlign: 'center', | ||
}, | ||
notesContainer: { | ||
alignSelf: 'center', | ||
width: '92%', | ||
marginTop: 20, | ||
marginBottom: 40, | ||
}, | ||
notesText: { | ||
textAlign: 'justify', | ||
fontSize: 13, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
import API from '@/api/authenticated-api' | ||
import { | ||
NoSessionError, | ||
UnavailableSessionError, | ||
} from '@/api/thi-session-handler' | ||
import LecturerRow from '@/components/Elements/Pages/LecturerRow' | ||
import Divider from '@/components/Elements/Universal/Divider' | ||
import { type Colors } from '@/stores/colors' | ||
import { | ||
type NormalizedLecturer, | ||
normalizeLecturers, | ||
} from '@/utils/lecturers-utils' | ||
import { useTheme } from '@react-navigation/native' | ||
import { useGlobalSearchParams, useRouter } from 'expo-router' | ||
import React, { useEffect, useState } from 'react' | ||
import { | ||
ActivityIndicator, | ||
RefreshControl, | ||
ScrollView, | ||
StyleSheet, | ||
Text, | ||
View, | ||
} from 'react-native' | ||
|
||
export default function LecturersCard(): JSX.Element { | ||
enum LoadingState { | ||
LOADING, | ||
LOADED, | ||
ERROR, | ||
REFRESHING, | ||
} | ||
|
||
const [personalLecturers, setPersonalLecturers] = useState< | ||
NormalizedLecturer[] | ||
>([]) | ||
const [filteredLecturers, setFilteredLecturers] = useState< | ||
NormalizedLecturer[] | ||
>([]) | ||
const [didFetch, setDidFetch] = useState(false) | ||
const [error, setError] = useState<Error | null>(null) | ||
const [loadingState, setLoadingState] = useState<LoadingState>( | ||
LoadingState.LOADING | ||
) | ||
const { q } = useGlobalSearchParams<{ q: string }>() | ||
const [allLecturers, setAllLecturers] = useState<NormalizedLecturer[]>([]) | ||
const colors = useTheme().colors as Colors | ||
const router = useRouter() | ||
async function load(): Promise<void> { | ||
try { | ||
const rawData = await API.getPersonalLecturers() | ||
const data = normalizeLecturers(rawData) | ||
setPersonalLecturers(data) | ||
setLoadingState(LoadingState.LOADED) | ||
} catch (e) { | ||
if ( | ||
e instanceof NoSessionError || | ||
e instanceof UnavailableSessionError | ||
) { | ||
router.replace('(user)/login') | ||
} else { | ||
setLoadingState(LoadingState.ERROR) | ||
setError(e as Error) | ||
} | ||
} | ||
} | ||
useEffect(() => { | ||
void load() | ||
}, []) | ||
|
||
const onRefresh: () => void = () => { | ||
void load() | ||
setLoadingState(LoadingState.LOADED) | ||
} | ||
|
||
useEffect(() => { | ||
async function load(): Promise<void> { | ||
setLoadingState(LoadingState.LOADING) | ||
if (q == null) { | ||
setFilteredLecturers(personalLecturers) | ||
setLoadingState(LoadingState.LOADED) | ||
return | ||
} | ||
|
||
if (allLecturers.length === 0) { | ||
if (didFetch) { | ||
return | ||
} | ||
|
||
setDidFetch(true) | ||
setFilteredLecturers([]) | ||
try { | ||
const rawData = await API.getLecturers('0', 'z') | ||
const data = normalizeLecturers(rawData) | ||
setAllLecturers(data) | ||
setLoadingState(LoadingState.LOADED) | ||
return | ||
} catch (e) { | ||
if (e instanceof NoSessionError) { | ||
router.replace('(user)/login') | ||
} else { | ||
setError(e as Error) | ||
} | ||
setLoadingState(LoadingState.ERROR) | ||
return | ||
} | ||
} | ||
|
||
const normalizedSearch = q.toLowerCase().trim() | ||
const checkField = (value: string | null): boolean => | ||
value?.toString().toLowerCase().includes(normalizedSearch) ?? | ||
false | ||
const filtered = allLecturers | ||
.filter( | ||
(x) => | ||
checkField(x.name) || | ||
checkField(x.vorname) || | ||
checkField(x.email) || | ||
checkField(x.tel_dienst) || | ||
checkField(x.raum) | ||
) | ||
.slice(0, 20) | ||
|
||
setFilteredLecturers(filtered) | ||
setLoadingState(LoadingState.LOADED) | ||
} | ||
void load() | ||
}, [didFetch, q, personalLecturers, allLecturers]) | ||
|
||
return ( | ||
<ScrollView | ||
contentInsetAdjustmentBehavior="automatic" | ||
refreshControl={ | ||
loadingState !== LoadingState.LOADING && | ||
loadingState !== LoadingState.LOADED ? ( | ||
<RefreshControl | ||
refreshing={loadingState === LoadingState.REFRESHING} | ||
onRefresh={onRefresh} | ||
/> | ||
) : undefined | ||
} | ||
> | ||
{loadingState === LoadingState.LOADING && ( | ||
<View style={styles.loadingContainer}> | ||
<ActivityIndicator size="small" color={colors.primary} /> | ||
</View> | ||
)} | ||
{loadingState === LoadingState.ERROR && ( | ||
<View> | ||
<Text style={[styles.errorMessage, { color: colors.text }]}> | ||
{error?.message} | ||
</Text> | ||
<Text style={[styles.errorInfo, { color: colors.text }]}> | ||
An error occurred while loading the data.{'\n'}Pull down | ||
to refresh. | ||
</Text> | ||
</View> | ||
)} | ||
|
||
{loadingState === LoadingState.LOADED && ( | ||
<View style={styles.loadedContainer}> | ||
<Text | ||
style={[ | ||
styles.sectionHeader, | ||
{ color: colors.labelSecondaryColor }, | ||
]} | ||
> | ||
{q != null ? 'Suchergebnisse' : 'Persönliche Dozenten'} | ||
</Text> | ||
<View | ||
style={[ | ||
styles.loadedRows, | ||
{ backgroundColor: colors.card }, | ||
]} | ||
> | ||
{filteredLecturers?.map((event, index) => ( | ||
<React.Fragment key={index}> | ||
<LecturerRow item={event} colors={colors} /> | ||
{index !== personalLecturers.length - 1 && ( | ||
<Divider | ||
color={colors.labelTertiaryColor} | ||
width={'90%'} | ||
/> | ||
)} | ||
</React.Fragment> | ||
))} | ||
</View> | ||
</View> | ||
)} | ||
</ScrollView> | ||
) | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
loadedContainer: { | ||
alignSelf: 'center', | ||
width: '95%', | ||
marginTop: 14, | ||
marginBottom: 24, | ||
}, | ||
loadedRows: { | ||
borderRadius: 8, | ||
}, | ||
errorMessage: { | ||
paddingTop: 100, | ||
fontWeight: '600', | ||
fontSize: 16, | ||
textAlign: 'center', | ||
}, | ||
errorInfo: { | ||
fontSize: 14, | ||
textAlign: 'center', | ||
marginTop: 10, | ||
}, | ||
loadingContainer: { | ||
paddingTop: 40, | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}, | ||
sectionHeader: { | ||
fontSize: 13, | ||
|
||
fontWeight: 'normal', | ||
textTransform: 'uppercase', | ||
marginBottom: 4, | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.