diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 5796e64..4f849b3 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -78,8 +78,15 @@ export default function TodayScreen() { collegeApi.profile().then((result) => { if (!active) return; setProfile(result); - setWeekendSchedule(Boolean(result.weekendSchedule)); - if (!result.weekendSchedule) setActiveDate((current) => current.getDay() === 0 || current.getDay() === 6 ? mondayOfWeek(current) : current); + const weekendsEnabled = Boolean(result.weekendSchedule); + setWeekendSchedule(weekendsEnabled); + if (!weekendsEnabled) { + setActiveDate((current) => { + if (current.getDay() === 6) return addDays(current, 2); + if (current.getDay() === 0) return addDays(current, 1); + return current; + }); + } }).catch(() => undefined); return () => { active = false; }; }, [])); @@ -139,13 +146,26 @@ export default function TodayScreen() { const updateStatus = (session: Session, status: AttendanceStatus) => { const previous = classes; setClasses((items) => items.map((item) => item.id === session.id ? { ...item, status } : item)); + setSummary((current) => { + const wasHeld = session.status === 'attended' || session.status === 'absent'; + const isHeld = status === 'attended' || status === 'absent'; + const total = current.total + (isHeld ? 1 : 0) - (wasHeld ? 1 : 0); + const attended = current.attended + (status === 'attended' ? 1 : 0) - (session.status === 'attended' ? 1 : 0); + return { total, attended, percentage: total ? Math.round((attended / total) * 100) : 0 }; + }); collegeApi.markAttendance(session.id, status) .then(() => { if (Platform.OS !== 'web') void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - setRefresh((v) => v + 1); }) .catch((error: Error) => { setClasses(previous); + setSummary((current) => { + const wasHeld = session.status === 'attended' || session.status === 'absent'; + const isHeld = status === 'attended' || status === 'absent'; + const total = current.total + (wasHeld ? 1 : 0) - (isHeld ? 1 : 0); + const attended = current.attended + (session.status === 'attended' ? 1 : 0) - (status === 'attended' ? 1 : 0); + return { total, attended, percentage: total ? Math.round((attended / total) * 100) : 0 }; + }); Alert.alert('Couldn’t save attendance', error.message); }); }; @@ -177,10 +197,13 @@ export default function TodayScreen() { const currentTimeLabel = formatCurrentTime(); const pendingCount = classes.filter((item) => item.status === 'pending').length; const metricTone = summary.total ? (insightTone === 'neutral' ? 'brand' : insightTone) : 'brand'; + const collapseCalendar = () => { + if (calendarExpanded) setCalendarExpanded(false); + }; return - + - + - + event.stopPropagation()}> {calendarExpanded ? ( - + - {isToday ? 'Today’s classes' : `${formatDayHeading(activeDate).split(',')[0]}’s classes`} + Schedule {classes.length ? `${classes.length} ${classes.length === 1 ? 'class' : 'classes'}${pendingCount ? ` · ${pendingCount} to mark` : ''}` : 'Your agenda for this date'} - setAddClassOpen(true)} /> - + {loadError ? } /> : null} {loading ? Loading your classes… : null} {!loading && !loadError && classes.length === 0 && !recessEnabled ? setAddClassOpen(true)} leading={} />} /> : null} @@ -453,7 +475,7 @@ const styles = StyleSheet.create({ // Bottom padding belongs to the scrollable class content, not this fixed shell. content: { paddingTop: spacing[1], paddingBottom: 0 }, - stickyHeader: { flexShrink: 0 }, + stickyHeader: { flexShrink: 0, position: 'relative', zIndex: 20, elevation: 20 }, headerRow: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', gap: spacing[2], marginBottom: spacing[3] }, headerTitle: { flex: 1, minHeight: 0 }, headerActions: { flexDirection: 'row', alignItems: 'center', gap: spacing[2], paddingTop: spacing[1] }, @@ -482,6 +504,9 @@ const styles = StyleSheet.create({ overviewTargetValue: { fontVariant: ['tabular-nums'] }, pickerContainer: { marginTop: spacing[1], + position: 'relative', + zIndex: 30, + elevation: 30, borderRadius: radius.feature, borderCurve: 'continuous', backgroundColor: colors.brand.skySoft, diff --git a/components/ui/bottom-tab-bar.tsx b/components/ui/bottom-tab-bar.tsx index 296f236..e340cb0 100644 --- a/components/ui/bottom-tab-bar.tsx +++ b/components/ui/bottom-tab-bar.tsx @@ -57,7 +57,7 @@ export function BottomTabBar({ state, navigation, insets }: BottomTabBarProps) { ); @@ -69,7 +69,7 @@ export function BottomTabBar({ state, navigation, insets }: BottomTabBarProps) { accessibilityLabel="Add a class" onPress={addClass} style={({ pressed }) => [styles.addButton, pressed && styles.addPressed]}> - + ); @@ -90,7 +90,7 @@ const styles = StyleSheet.create({ }, navigationGroup: { flex: 1, - minHeight: 58, + minHeight: 64, flexDirection: 'row', alignItems: 'center', gap: spacing[1], @@ -112,16 +112,16 @@ const styles = StyleSheet.create({ borderCurve: 'continuous', }, tabActive: { - backgroundColor: colors.brand.coral, + backgroundColor: colors.brand.cobalt, }, addButton: { - width: 58, - height: 58, + width: 64, + height: 64, alignItems: 'center', justifyContent: 'center', borderRadius: radius.feature, borderCurve: 'continuous', - backgroundColor: colors.brand.coral, + backgroundColor: colors.brand.cobalt, ...shadow.floating, }, pressed: { @@ -129,7 +129,7 @@ const styles = StyleSheet.create({ transform: [{ scale: 0.97 }], }, addPressed: { - backgroundColor: colors.brand.coralSoft, + backgroundColor: colors.brand.cobaltPressed, transform: [{ scale: 0.96 }], }, });