From ac6bfa1022431dc483655c11509d08f8ff6e84b4 Mon Sep 17 00:00:00 2001 From: Chris Sanden Date: Tue, 5 May 2026 20:58:03 +0200 Subject: [PATCH] don't ask me how, but it works --- app/(tabs)/subjects.tsx | 13 +- app/subject/viewDetailsSubject.tsx | 55 ++- app/task/viewDetailsTask.tsx | 653 +++++++++++++---------------- 3 files changed, 327 insertions(+), 394 deletions(-) diff --git a/app/(tabs)/subjects.tsx b/app/(tabs)/subjects.tsx index f57a860..62d82eb 100644 --- a/app/(tabs)/subjects.tsx +++ b/app/(tabs)/subjects.tsx @@ -8,8 +8,6 @@ import { Redirect, router, Stack, useFocusEffect } from 'expo-router'; import { useCallback, useEffect, useState } from 'react'; import { Alert, Modal, Pressable, ScrollView, Text, View, ActivityIndicator } from 'react-native'; -import type { SubjectColor } from '@/lib/subjectColors'; - const FLOW_STEPS = [ { label: '1', @@ -77,8 +75,6 @@ export default function Subjects() { }, [session?.user.id]); const GetSubjects = useCallback(async () => { - if (!session?.user.id) return; - const GetSubjects = async () => { if (!session?.user.id) { SetIsLoading(false); return; @@ -86,26 +82,21 @@ export default function Subjects() { SetIsLoading(true); - SetIsLoading(true); - const { data, error } = await supabase .from('subjects') .select('*') .eq('uId', session.user.id) .order('lastChanged', { ascending: false }); - SetIsLoading(false); + SetIsLoading(false); if (error) { Alert.alert('Subjects could not be fetched, please try again'); - SetIsLoading(false); return; } SetSubjects((data as Subject[]) ?? []); }, [session?.user.id]); - SetIsLoading(false); - }; useFocusEffect( useCallback(() => { @@ -121,6 +112,8 @@ export default function Subjects() { if (needsSetup) { return ; + } + const RenderSubjectCard = (subject: Subject) => { const colorKey: SubjectColor = subject.color ?? 'slate'; const colorSet = SUBJECT_COLORS[colorKey]; diff --git a/app/subject/viewDetailsSubject.tsx b/app/subject/viewDetailsSubject.tsx index 5658457..cf27a22 100644 --- a/app/subject/viewDetailsSubject.tsx +++ b/app/subject/viewDetailsSubject.tsx @@ -348,39 +348,36 @@ export default function ViewDetailsSubject() { - Assignments completed + Assignment Progress - {totalAssignments > 0 ? ( - - - - Assignment Progress - - - {completedAssignments}/{totalAssignments} - - - - - - - - - {remainingAssignments === 0 - ? 'All assignments complete' - : `${remainingAssignments} assignment${ - remainingAssignments === 1 ? '' : 's' - } remaining`} + + {completedAssignments}/{totalAssignments} - ) : null} + + + + + + + {remainingAssignments === 0 + ? 'All assignments complete' + : `${remainingAssignments} assignment${ + remainingAssignments === 1 ? '' : 's' + } remaining`} + + + + Based only on completed assignments in this subject. + + Last changed: {formatDateTime(subject.lastChanged)} diff --git a/app/task/viewDetailsTask.tsx b/app/task/viewDetailsTask.tsx index fda188f..7391c7e 100644 --- a/app/task/viewDetailsTask.tsx +++ b/app/task/viewDetailsTask.tsx @@ -31,65 +31,46 @@ function formatTrackedTime(totalSeconds: number) { } export default function ViewDetailsTask() { -const { tId } = useLocalSearchParams<{ tId: string }>(); + const { tId } = useLocalSearchParams<{ tId: string }>(); -const [task, SetTask] = useState(null); -const [session, SetSession] = useState(null); -const [completedFocusSessions, setCompletedFocusSessions] = useState(0); -const [contextMeta, setContextMeta] = useState({ - subjectTitle: 'No Subject', - assignmentTitle: 'No Assignment', - subjectColor: 'slate' as SubjectColor, -}); - -useEffect(() => { - supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null)); - - const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => { - SetSession(newSession); + const [task, SetTask] = useState(null); + const [session, SetSession] = useState(null); + const [isLoading, SetIsLoading] = useState(false); + const [completedFocusSessions, setCompletedFocusSessions] = useState(0); + const [contextMeta, setContextMeta] = useState({ + subjectTitle: 'No Subject', + assignmentTitle: 'No Assignment', + subjectColor: 'slate' as SubjectColor, }); - return () => sub.subscription.unsubscribe(); -}, []); + useEffect(() => { + supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null)); -const loadTaskStudyActivity = useCallback(async (taskId: string, userId: string) => { - const { count, error } = await supabase - .from('sprint_sessions') - .select('sessionId', { count: 'exact', head: true }) - .eq('taskId', taskId) - .eq('userId', userId) - .eq('sessionType', 'focus') - .eq('status', 'completed'); + const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => { + SetSession(newSession); + }); - if (error) { - setCompletedFocusSessions(0); - return; - } + return () => sub.subscription.unsubscribe(); + }, []); - setCompletedFocusSessions(count ?? 0); -}, []); + const loadTaskStudyActivity = useCallback(async (taskId: string, userId: string) => { + const { count, error } = await supabase + .from('sprint_sessions') + .select('sessionId', { count: 'exact', head: true }) + .eq('taskId', taskId) + .eq('userId', userId) + .eq('sessionType', 'focus') + .eq('status', 'completed'); -const GetTask = useCallback(async (taskId: string) => { - const { data, error } = await supabase - .from('tasks') - .select('*') - .eq('tId', taskId) - .single(); + if (error) { + setCompletedFocusSessions(0); + return; + } - if (error || !data) { - Alert.alert('Task could not be fetched, please try again'); - return; - } + setCompletedFocusSessions(count ?? 0); + }, []); - SetTask(data); - await loadTaskStudyActivity(taskId, data.uId); - - if (data.aId) { - const { data: assignmentData, error: assignmentError } = await supabase - .from('assignments') - .select('title, sId') - .eq('aId', data.aId) - const GetTask = async (taskId: string) => { + const GetTask = useCallback(async (taskId: string) => { SetIsLoading(true); const { data, error } = await supabase @@ -97,130 +78,113 @@ const GetTask = useCallback(async (taskId: string) => { .select('*') .eq('tId', taskId) .single(); - - SetIsLoading(false); - if (assignmentError || !assignmentData) { + if (error || !data) { + SetTask(null); setContextMeta({ subjectTitle: 'Unknown Subject', assignmentTitle: 'Unknown Assignment', subjectColor: 'slate', }); + setCompletedFocusSessions(0); + SetIsLoading(false); + Alert.alert('Task could not be fetched, please try again'); return; } - if (assignmentData.sId) { - const { data: subjectData, error: subjectError } = await supabase - .from('subjects') - .select('title, color') - .eq('sId', assignmentData.sId) - .single(); - - if (subjectError || !subjectData) { SetTask(data); + await loadTaskStudyActivity(taskId, data.uId); + + let nextContextMeta = { + subjectTitle: 'Unknown Subject', + assignmentTitle: 'Unknown Assignment', + subjectColor: 'slate' as SubjectColor, + }; if (data.aId) { - SetIsLoading(true); - const { data: assignmentData, error: assignmentError } = await supabase .from('assignments') .select('title, sId') .eq('aId', data.aId) .single(); - SetIsLoading(false); + if (!assignmentError && assignmentData) { + nextContextMeta.assignmentTitle = assignmentData.title ?? 'Unknown Assignment'; - if (assignmentError || !assignmentData) { - setContextMeta({ - subjectTitle: 'Unknown Subject', - assignmentTitle: assignmentData.title ?? 'Unknown Assignment', - subjectColor: 'slate', - }); - return; - } + if (assignmentData.sId) { + const { data: subjectData, error: subjectError } = await supabase + .from('subjects') + .select('title, color') + .eq('sId', assignmentData.sId) + .single(); - setContextMeta({ - subjectTitle: subjectData.title ?? 'Unknown Subject', - assignmentTitle: assignmentData.title ?? 'Unknown Assignment', - subjectColor: (subjectData.color as SubjectColor | undefined) ?? 'slate', - }); - } - } -}, [loadTaskStudyActivity]); - if (assignmentData.sId) { - SetIsLoading(true); - - const { data: subjectData, error: subjectError } = await supabase - .from('subjects') - .select('title, color') - .eq('sId', assignmentData.sId) - .single(); - - SetIsLoading(false); - - if (subjectError || !subjectData) { - setContextMeta({ - subjectTitle: 'Unknown Subject', - assignmentTitle: assignmentData.title ?? 'Unknown Assignment', - subjectColor: 'slate', - }); - return; + if (!subjectError && subjectData) { + nextContextMeta = { + subjectTitle: subjectData.title ?? 'Unknown Subject', + assignmentTitle: assignmentData.title ?? 'Unknown Assignment', + subjectColor: (subjectData.color as SubjectColor | undefined) ?? 'slate', + }; + } } - -useFocusEffect( - useCallback(() => { - if (session && tId) { - GetTask(tId); - } - }, [GetTask, session, tId]) -); - -const handleSprintStart = async () => { - const activeSession = await GetActiveSession(); - - if (!activeSession) { - router.push({ - pathname: '/task/timer', - params: { - tId: task?.tId, - durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), - }, - }); - return; - } - - - - const secondsLeft = Math.ceil((activeSession.endTime - Date.now()) / 1000) - - if (secondsLeft <= 0) { - await finalizeStoredSession('expired', activeSession); - router.push({ - pathname: '/task/timer', - params: { - tId: task?.tId, - durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), } - }); - return; } - - if (activeSession.taskId === task?.tId) { - router.push({ - pathname: '/task/timer', - params: { - tId: activeSession.taskId ?? undefined, - durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), - }}); - return; + setContextMeta(nextContextMeta); + SetIsLoading(false); + }, [loadTaskStudyActivity]); + + useFocusEffect( + useCallback(() => { + if (session && tId) { + void GetTask(tId); + } + }, [GetTask, session, tId]) + ); + + const handleSprintStart = async () => { + const activeSession = await GetActiveSession(); + + if (!activeSession) { + router.push({ + pathname: '/task/timer', + params: { + tId: task?.tId, + durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), + }, + }); + return; + } + + const secondsLeft = Math.ceil((activeSession.endTime - Date.now()) / 1000); + + if (secondsLeft <= 0) { + await finalizeStoredSession('expired', activeSession); + router.push({ + pathname: '/task/timer', + params: { + tId: task?.tId, + durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), + }, + }); + return; + } + + if (activeSession.taskId === task?.tId) { + router.push({ + pathname: '/task/timer', + params: { + tId: activeSession.taskId ?? undefined, + durationMinutes: String(DEFAULT_FOCUS_DURATION_MINUTES), + }, + }); + return; } Alert.alert( - 'Active session in progress', + 'Active session in progress', `End the current session and start a new ${DEFAULT_FOCUS_DURATION_MINUTES} minute sprint on this task?`, [ - { text: 'Cancel', style: 'cancel', }, + { text: 'Cancel', style: 'cancel' }, { text: 'Start new sprint', style: 'destructive', @@ -239,6 +203,47 @@ const handleSprintStart = async () => { ); }; + const DeleteTask = async (taskId: string) => { + Alert.alert( + 'Delete Task', + 'Are you sure you want to delete this task?', + [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Delete', + style: 'destructive', + onPress: async () => { + const { error } = await supabase + .from('tasks') + .delete() + .eq('tId', taskId); + + if (error) { + Alert.alert('Task could not be deleted, please try again'); + return; + } + + const aId = task?.aId; + + if (aId) { + try { + await CheckAssignmentCompletion(aId); + } catch { + Alert.alert('Failed to update assignment completion state'); + } + } + + Alert.alert('Task deleted successfully!'); + router.back(); + }, + }, + ] + ); + }; + const colorSet = getSubjectColorSet(contextMeta.subjectColor); if (isLoading) { @@ -268,56 +273,41 @@ const handleSprintStart = async () => { }} /> + + + Task not found + + + The task could not be loaded. + -const DeleteTask = async (taskId: string) => { - Alert.alert( - 'Delete Task', - 'Are you sure you want to delete this task?', - [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Delete', - style: 'destructive', - onPress: async () => { - const { error } = await supabase - .from('tasks') - .delete() - .eq('tId', taskId); + router.back()} + > + + Go back + + + + + ); + } - if (error) { - Alert.alert('Task could not be deleted, please try again'); - return; - } + const isOwner = session?.user.id === task.uId; - const aId = task?.aId; - - if (aId) { - try { - await CheckAssignmentCompletion(aId); - } catch { - Alert.alert('Failed to update assignment completion state'); - } - } - - Alert.alert('Task deleted successfully!'); - router.back(); - }, - }, - ] - ); -}; - -const colorSet = getSubjectColorSet(contextMeta.subjectColor); - -if (!task) { return ( ( - - Task not found - - - The task could not be loaded. - - - router.back()} - > - - Go back - - - - - ); -} - -const isOwner = session?.user.id === task.uId; - -return ( - - ( - await supabase.auth.signOut()} + + - - Logout - - - ), - }} - /> + {task.isCompleted ? ( + + ) : null} + - - - - {task.isCompleted && ( - - )} - - - - - {task.title} - - - {task.description ? ( - - {task.description} - - ) : ( - - No description added. - - )} - - - + - + + {task.description ? ( + + {task.description} + + ) : ( + + No description added. + + )} + + + - {contextMeta.subjectTitle} - - - - - - {contextMeta.assignmentTitle} - - - - - - Status: {task.isCompleted ? 'Completed' : 'Not completed'} - - - - - - - Study activity - - - This tracks focused work on the task separately from whether the task is marked completed. - - - - - - Focus time - - - {formatTrackedTime(task.totalTimeInSeconds ?? 0)} + + {contextMeta.subjectTitle} - - - Completed sessions + + + {contextMeta.assignmentTitle} - - {completedFocusSessions} + + + + + Status: {task.isCompleted ? 'Completed' : 'Not completed'} + + + + Study activity + + + This tracks focused work on the task separately from whether the task is marked completed. + + + + + + Focus time + + + {formatTrackedTime(task.totalTimeInSeconds ?? 0)} + + + + + + Completed sessions + + + {completedFocusSessions} + + + + + + + Last changed: {formatDateTime(task.lastChanged)} + - - - Last changed: {formatDateTime(task.lastChanged)} - + + {isOwner ? ( + + + + Start Sprint + + + + + Starts a {DEFAULT_FOCUS_DURATION_MINUTES} minute focus sprint for this task. + + + + + router.push({ + pathname: '/task/upsertTask', + params: { tId: task.tId }, + }) + } + > + + Edit + + + + DeleteTask(task.tId)} + > + + Delete + + + + + ) : null} - - {isOwner && ( - - handleSprintStart()} - > - - Start Sprint - - - - - Starts a {DEFAULT_FOCUS_DURATION_MINUTES} minute focus sprint for this task. - - - - - router.push({ - pathname: '/task/upsertTask', - params: { tId: task.tId }, - }) - } - > - - Edit - - - DeleteTask(task.tId)} - > - - Delete - - - - - )} - ); }