diff --git a/app.json b/app.json
index b7c46f2..e1b0afe 100644
--- a/app.json
+++ b/app.json
@@ -38,7 +38,8 @@
"backgroundColor": "#000000"
}
}
- ]
+ ],
+ "expo-secure-store"
],
"experiments": {
"typedRoutes": true,
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index a028b63..ef2b145 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -33,8 +33,10 @@ export default function TabLayout() {
return (
-
+
+
+
);
}
\ No newline at end of file
diff --git a/app/(tabs)/assignments.tsx b/app/(tabs)/assignments.tsx
new file mode 100644
index 0000000..6cc5358
--- /dev/null
+++ b/app/(tabs)/assignments.tsx
@@ -0,0 +1,147 @@
+import { defaultStyles } from "@/constants/defaultStyles";
+import { supabase } from "@/lib/supabase";
+import { Ionicons } from '@expo/vector-icons';
+import { Session } from "@supabase/supabase-js";
+import { router, Stack, useFocusEffect } from "expo-router";
+import { useCallback, useEffect, useState } from "react";
+import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
+
+type Assignment = {
+ aId: string;
+ title: string;
+ description: string;
+ deadline: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ sId: string;
+}
+
+export default function Assignments() {
+ const [assignments, SetAssignments] = useState([])
+ const [session, SetSession] = useState(null)
+
+ const assignmentSections = [
+ { title: "Upcoming Assignments", data: assignments.filter((assignment) => !assignment.isCompleted), emptyMessage: "No upcoming assignments" },
+ { title: "Completed Assignments", data: assignments.filter((assignment) => assignment.isCompleted), emptyMessage: "No completed assignments" },
+ ];
+
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null))
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession)
+ })
+ return () => sub.subscription.unsubscribe()
+ },
+ [])
+
+ const GetAssignments = async () => {
+ const { data, error } = await supabase.from("assignments").select("*").order("deadline", { ascending: false });
+
+ if (error) {
+ Alert.alert("Assignments could not be fetched, please try again");
+ return;
+ }
+
+ SetAssignments(data ?? []);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session) {
+ GetAssignments();
+ }
+ }, [session])
+ );
+
+ const DeleteAssignment = async (aId: string) => {
+ Alert.alert(
+ "Delete Assignment",
+ "Are you sure you want to delete this assignment?",
+ [
+ {
+ text: "Cancel",
+ style: "cancel"
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ const { error } = await supabase.from("assignments").delete().eq("aId", aId);
+
+ if (error) {
+ Alert.alert("Assignment could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Assignment deleted successfully!");
+ GetAssignments();
+ }
+ }
+ ]
+ )
+ }
+
+ return (
+
+ {
+ return (
+
+
+
+
+
+ )
+ },
+ }}
+ />
+
+
+
+
+ item.aId}
+ renderSectionHeader={({ section: { title } }) => {title}}
+ renderItem={({ item }) => {
+ const isOwner = session?.user.id === item.uId;
+
+ return (
+
+ router.push({pathname: "/assignment/viewDetailsAssignment", params: { aId: item.aId }})}>
+ {item.title}
+ {item.deadline}
+
+ {item.isCompleted && ✓}
+
+
+
+ {isOwner && (
+
+
+ )}
+
+ );
+ }}
+ renderSectionFooter={({ section }) =>
+ section.data.length === 0 ? (
+
+ {section.emptyMessage}
+
+
+ ) : (
+
+ )
+ }
+ />
+
+ )
+}
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 2266616..c1ae979 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -8,7 +8,7 @@ export default function HomeScreen() {
{
return (
diff --git a/app/(tabs)/subjects.tsx b/app/(tabs)/subjects.tsx
new file mode 100644
index 0000000..41b4946
--- /dev/null
+++ b/app/(tabs)/subjects.tsx
@@ -0,0 +1,144 @@
+import { defaultStyles } from "@/constants/defaultStyles";
+import { supabase } from "@/lib/supabase";
+import { Ionicons } from '@expo/vector-icons';
+import { Session } from "@supabase/supabase-js";
+import { router, Stack, useFocusEffect } from "expo-router";
+import { useCallback, useEffect, useState } from "react";
+import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
+
+type Subject = {
+ sId: string;
+ title: string;
+ description: string;
+ isActive: boolean;
+ lastChanged: string;
+ uId: string;
+}
+
+export default function Subjects() {
+ const [subjects, SetSubject] = useState([])
+ const [session, SetSession] = useState(null)
+
+ const subjectSections = [
+ { title: "Active Subjects", data: subjects.filter((subject) => !subject.isActive), emptyMessage: "No active subjects" },
+ { title: "Inactive Subjects", data: subjects.filter((subject) => subject.isActive), emptyMessage: "No inactive subjects" },
+ ];
+
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null))
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession)
+ })
+ return () => sub.subscription.unsubscribe()
+ },
+ [])
+
+ const GetSubjects = async () => {
+ const { data, error } = await supabase.from("subjects").select("*");
+
+ if (error) {
+ Alert.alert("Subjects could not be fetched, please try again");
+ return;
+ }
+
+ SetSubject(data ?? []);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session) {
+ GetSubjects();
+ }
+ }, [session])
+ );
+
+ const DeleteSubject = async (sId: string) => {
+ Alert.alert(
+ "Delete Subject",
+ "Are you sure you want to delete this subject?",
+ [
+ {
+ text: "Cancel",
+ style: "cancel"
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ const { error } = await supabase.from("subjects").delete().eq("sId", sId);
+
+ if (error) {
+ Alert.alert("Subject could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Subject deleted successfully!");
+ GetSubjects();
+ }
+ }
+ ]
+ )
+ }
+
+ return (
+
+ {
+ return (
+
+
+
+
+ await supabase.auth.signOut()} />
+
+ )
+ },
+ }}
+ />
+
+
+ router.push("/subject/createSubject")} />
+
+
+ item.sId}
+ renderSectionHeader={({ section: { title } }) => {title}}
+ renderItem={({ item }) => {
+ const isOwner = session?.user.id === item.uId;
+
+ return (
+
+ router.push({pathname: "/subject/viewDetailsSubject", params: { sId: item.sId }})}>
+ {item.title}
+
+ {item.isActive && ✓}
+
+
+
+ {isOwner && (
+
+ router.push({pathname: "/subject/editSubject", params: { sId: item.sId }})} />
+ DeleteSubject(item.sId)} />
+
+ )}
+
+ );
+ }}
+ renderSectionFooter={({ section }) =>
+ section.data.length === 0 ? (
+
+ {section.emptyMessage}
+
+
+ ) : (
+
+ )
+ }
+ />
+
+ )
+}
diff --git a/app/(tabs)/tasks.tsx b/app/(tabs)/tasks.tsx
index b18561e..47ed0ac 100644
--- a/app/(tabs)/tasks.tsx
+++ b/app/(tabs)/tasks.tsx
@@ -12,8 +12,8 @@ type Task = {
description: string;
isCompleted: boolean;
lastChanged: string;
- deadline: string;
uId: string;
+ aId: string;
}
export default function Tasks() {
@@ -34,6 +34,17 @@ export default function Tasks() {
},
[])
+ const GetTasks = async () => {
+ const { data, error } = await supabase.from("tasks").select("*");
+
+ if (error) {
+ Alert.alert("Tasks could not be fetched, please try again");
+ return;
+ }
+
+ SetTasks(data ?? []);
+ }
+
useFocusEffect(
useCallback(() => {
if (session) {
@@ -42,17 +53,6 @@ export default function Tasks() {
}, [session])
);
- const GetTasks = async () => {
- const { data, error } = await supabase.from("tasks").select("tId, title, description, isCompleted,lastChanged, deadline, uId").order("deadline", { ascending: false });
-
- if (error) {
- Alert.alert("Task could not be fetched, please try again");
- return;
- }
-
- SetTasks(data ?? []);
- }
-
const DeleteTask = async (tId: string) => {
Alert.alert(
"Delete Task",
@@ -101,7 +101,7 @@ export default function Tasks() {
/>
- router.push("/createTask")} />
+ router.push("/task/createTask")} />
- {item.title}
- {item.deadline}
+ router.push({pathname: "/task/viewDetailsTask", params: { tId: item.tId }})}>
+ {item.title}
+
+ {item.isCompleted && ✓}
+
+
{isOwner && (
- router.push({pathname: "/editTask", params: { tId: item.tId }})} />
+ router.push({pathname: "/task/editTask", params: { tId: item.tId }})} />
DeleteTask(item.tId)} />
)}
diff --git a/app/(tabs)/editTask.tsx b/app/assignment/createAssignment.tsx
similarity index 68%
rename from app/(tabs)/editTask.tsx
rename to app/assignment/createAssignment.tsx
index 9cce4b0..0845549 100644
--- a/app/(tabs)/editTask.tsx
+++ b/app/assignment/createAssignment.tsx
@@ -1,43 +1,20 @@
import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase';
-import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
-import { useCallback, useState } from 'react';
+import { router, Stack, useLocalSearchParams } from 'expo-router';
+import { useState } from 'react';
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
-export default function EditTask() {
+export default function CreateAssignment() {
+ const sId = (useLocalSearchParams().sId as string) ?? null;
const [title, SetTitle] = useState('');
const [description, SetDescription] = useState('');
- const [isCompleted, SetIsCompleted] = useState(false);
const [deadline, SetDeadline] = useState('');
+ const [isCompleted, SetIsCompleted] = useState(false);
const [isSaving, SetIsSaving] = useState(false);
- const { tId } = useLocalSearchParams();
- useFocusEffect(
- useCallback(() => {
- const GetTask = async () => {
- if (!tId) return;
-
- const { data, error } = await supabase.from("tasks").select("*").eq("tId", tId).single();
-
- if (error) {
- Alert.alert("Task not found");
- return;
- }
-
- if (data) {
- SetTitle(data.title);
- SetDescription(data.description);
- SetIsCompleted(data.isCompleted);
- SetDeadline(data.deadline);
- }
- }
- GetTask();
- }, [tId])
- );
-
- const EditTask = async () => {
- if(title.trim() === '' || description.trim() === '' || deadline.trim() === '') {
- Alert.alert("All fields are required!");
+ const CreateAssignment = async () => {
+ if(title.trim() === '' || deadline.trim() === '') {
+ Alert.alert("Title and deadline are required!");
return;
}
@@ -50,26 +27,27 @@ export default function EditTask() {
SetIsSaving(true);
- const { error: dbError } = await supabase.from("tasks").update({
+ const { error: dbError } = await supabase.from("assignments").insert({
title,
description,
+ deadline,
isCompleted,
lastChanged: new Date().toISOString(),
- deadline,
uId: data.user.id,
- }).eq("tId", tId);
+ sId: sId,
+ });
if (dbError) {
- Alert.alert("Task could not be edited, please try again");
+ Alert.alert("Assignment could not be created, please try again");
return;
}
- Alert.alert("Task successfully edited!");
+ Alert.alert("Assignment successfully created!");
SetTitle('');
SetDescription('');
- SetIsCompleted(false);
SetDeadline('');
+ SetIsCompleted(false);
SetIsSaving(false);
@@ -80,25 +58,25 @@ export default function EditTask() {
<>
- Edit Task
+ Create New Assignment
@@ -118,7 +96,7 @@ export default function EditTask() {
{isCompleted ? 'Completed' : 'Not completed'}
-
+
{isSaving && (
)}
diff --git a/app/assignment/editAssignment.tsx b/app/assignment/editAssignment.tsx
new file mode 100644
index 0000000..f558deb
--- /dev/null
+++ b/app/assignment/editAssignment.tsx
@@ -0,0 +1,143 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useState } from 'react';
+import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
+
+type Assignment = {
+ aId: string;
+ title: string;
+ description: string;
+ deadline: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ sId: string;
+}
+
+export default function EditAssignment() {
+ const { aId } = useLocalSearchParams<{ aId: string }>();
+ const [assignment, SetAssignment] = useState(null)
+ const [isSaving, SetIsSaving] = useState(false);
+
+ const GetAssignment = async (aId: string) => {
+ const { data, error } = await supabase.from("assignments").select("*").eq("aId", aId).single();
+
+ if (error) {
+ Alert.alert("Assignment could not be fetched, please try again");
+ return;
+ }
+
+ SetAssignment(data ?? null);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (aId) {
+ GetAssignment(aId);
+ }
+ }, [aId])
+ );
+
+ const EditAssignment = async () => {
+ if (!assignment) return;
+
+ if(assignment.title.trim() === '' || assignment.deadline.trim() === '') {
+ Alert.alert("Title and deadline are required!");
+ return;
+ }
+
+ const { data, error: userError } = await supabase.auth.getUser();
+
+ if(userError || !data.user) {
+ router.replace("../createUser");
+ return;
+ }
+
+ SetIsSaving(true);
+
+ const { error: dbError } = await supabase.from("assignments").update({
+ title: assignment.title,
+ description: assignment.description,
+ deadline: assignment.deadline,
+ isCompleted: assignment.isCompleted,
+ lastChanged: new Date().toISOString(),
+ uId: data.user.id,
+ sId: assignment.sId,
+ }).eq("aId", aId);
+
+ SetIsSaving(false);
+
+ if (dbError) {
+ Alert.alert("Assignment could not be edited, please try again");
+ return;
+ }
+
+ Alert.alert("Assignment successfully edited!");
+
+ router.back();
+ }
+
+ return (
+
+
+
+ {!assignment && (
+
+ Assignment not found
+
+ )}
+
+ {assignment && (
+
+ Edit Assignment
+
+
+
+ SetAssignment(prev => prev ? { ...prev, title: text } : prev)}
+ />
+ SetAssignment(prev => prev ? { ...prev, description: text } : prev)}
+ />
+ SetAssignment(prev => prev ? { ...prev, deadline: text } : prev)}
+ />
+ SetAssignment(prev => prev ? { ...prev, isCompleted: !prev.isCompleted } : prev)}
+ style={defaultStyles.checkboxContainer}
+ >
+
+ {assignment.isCompleted && ✓}
+
+ {assignment.isCompleted ? 'Completed' : 'Not Completed'}
+
+
+
+ {isSaving && (
+
+ )}
+ router.back()} />
+
+
+
+
+ )}
+
+ )
+}
+
diff --git a/app/assignment/viewDetailsAssignment.tsx b/app/assignment/viewDetailsAssignment.tsx
new file mode 100644
index 0000000..e1548a7
--- /dev/null
+++ b/app/assignment/viewDetailsAssignment.tsx
@@ -0,0 +1,224 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { Session } from '@supabase/supabase-js';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useEffect, useState } from 'react';
+import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
+
+type Assignment = {
+ aId: string;
+ title: string;
+ description: string;
+ deadline: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ sId: string;
+}
+
+type Task = {
+ tId: string;
+ title: string;
+ description: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ aId: string;
+}
+
+export default function ViewDetailsAssignment() {
+ const { aId } = useLocalSearchParams<{ aId: string }>();
+ const [assignment, SetAssignment] = useState(null)
+ const [tasks, SetTasks] = useState([])
+ const [session, SetSession] = useState(null)
+
+ const taskSections = [
+ { title: "Upcoming Tasks", data: tasks.filter((task) => !task.isCompleted), emptyMessage: "No upcoming tasks" },
+ { title: "Completed Tasks", data: tasks.filter((task) => task.isCompleted), emptyMessage: "No completed tasks" },
+ ];
+
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null))
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession)
+ })
+ return () => sub.subscription.unsubscribe()
+ },
+ [])
+
+ const GetAssignment = async (aId: string) => {
+ const { data, error } = await supabase.from("assignments").select("*").eq("aId", aId).single();
+
+ if (error) {
+ Alert.alert("Assignment could not be fetched, please try again");
+ return;
+ }
+
+ SetAssignment(data ?? null);
+ }
+
+ const GetTasks = async (aId: string) => {
+ const { data, error } = await supabase.from("tasks").select("*").eq("aId", aId);
+
+ if (error) {
+ Alert.alert("Tasks could not be fetched, please try again");
+ return;
+ }
+
+ SetTasks(data ?? []);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session && aId) {
+ GetAssignment(aId);
+ GetTasks(aId);
+ }
+ }, [session, aId])
+ );
+
+ const DeleteAssignment = async (aId: string) => {
+ Alert.alert(
+ "Delete Assignment",
+ "Are you sure you want to delete this assignment?",
+ [
+ {
+ text: "Cancel",
+ style: "cancel"
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ const { error } = await supabase.from("assignments").delete().eq("aId", aId);
+
+ if (error) {
+ Alert.alert("Assignment could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Assignment deleted successfully!");
+ router.back();
+ }
+ }
+ ]
+ )
+ }
+
+ const DeleteTask = async (tId: string, aId: 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", tId);
+
+ if (error) {
+ Alert.alert("Task could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Task deleted successfully!");
+ GetTasks(aId);
+ }
+ }
+ ]
+ )
+ }
+
+ return (
+
+ {
+ return (
+
+
+
+ )
+ },
+ headerRight: () => {
+ return (
+
+ await supabase.auth.signOut()} />
+
+ )
+ },
+ }}
+ />
+
+ {!assignment && (
+
+ Assignment not found
+
+ )}
+
+ {assignment && (
+
+
+ {assignment.title}
+ {assignment.description}
+ {assignment.deadline}
+
+ {assignment.isCompleted && ✓}
+
+ {assignment.lastChanged}
+
+ router.push({pathname: "/assignment/editAssignment", params: { aId: assignment.aId }})} />
+ DeleteAssignment(assignment.aId)} />
+
+
+
+ router.push({pathname: "/task/createTask", params: { aId: assignment.aId }})} />
+
+
+ item.tId}
+ renderSectionHeader={({ section: { title } }) => {title}}
+ renderItem={({ item }) => {
+ const isOwner = session?.user.id === item.uId;
+
+ return (
+
+ router.push({pathname: "/task/viewDetailsTask", params: { tId: item.tId }})}>
+ {item.title}
+
+ {item.isCompleted && ✓}
+
+
+
+ {isOwner && (
+
+ router.push({pathname: "/task/editTask", params: { tId: item.tId }})} />
+ DeleteTask(item.tId, item.tId)} />
+
+ )}
+
+ );
+ }}
+ renderSectionFooter={({ section }) =>
+ section.data.length === 0 ? (
+
+ {section.emptyMessage}
+
+
+ ) : (
+
+ )
+ }
+ />
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/app/subject/createSubject.tsx b/app/subject/createSubject.tsx
new file mode 100644
index 0000000..33d95df
--- /dev/null
+++ b/app/subject/createSubject.tsx
@@ -0,0 +1,100 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { router, Stack } from 'expo-router';
+import { useState } from 'react';
+import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
+
+export default function CreateTask() {
+ const [title, SetTitle] = useState('');
+ const [description, SetDescription] = useState('');
+ const [isActive, SetIsActive] = useState(true);
+ const [isSaving, SetIsSaving] = useState(false);
+
+ const CreateSubject = async () => {
+ if(title.trim() === '') {
+ Alert.alert("Title is required!");
+ return;
+ }
+
+ const { data, error: userError } = await supabase.auth.getUser();
+
+ if(userError || !data.user) {
+ router.replace("../createUser");
+ return;
+ }
+
+ SetIsSaving(true);
+
+ const { error: dbError } = await supabase.from("subjects").insert({
+ title,
+ description,
+ isActive,
+ lastChanged: new Date().toISOString(),
+ uId: data.user.id,
+ });
+
+ if (dbError) {
+ Alert.alert("Subject could not be created, please try again");
+ return;
+ }
+
+ Alert.alert("Subject successfully created!");
+
+ SetTitle('');
+ SetDescription('');
+ SetIsActive(false);
+
+ SetIsSaving(false);
+
+ router.back();
+ }
+
+ return (
+ <>
+
+
+
+ Create New Subject
+
+
+
+
+
+ SetIsActive(state => !state)}
+ style={defaultStyles.checkboxContainer}
+ >
+
+ {isActive && ✓}
+
+ {isActive ? 'Active' : 'Inactive'}
+
+
+
+ {isSaving && (
+
+ )}
+ router.back()} />
+
+
+
+
+ >
+ )
+}
+
diff --git a/app/subject/editSubject.tsx b/app/subject/editSubject.tsx
new file mode 100644
index 0000000..d864cce
--- /dev/null
+++ b/app/subject/editSubject.tsx
@@ -0,0 +1,133 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useState } from 'react';
+import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
+
+type Subject = {
+ sId: string;
+ title: string;
+ description: string;
+ isActive: boolean;
+ lastChanged: string;
+ uId: string;
+}
+
+export default function EditSubject() {
+ const { sId } = useLocalSearchParams<{ sId: string }>();
+ const [subject, SetSubject] = useState(null)
+ const [isSaving, SetIsSaving] = useState(false);
+
+ const GetSubject = async (sId: string) => {
+ const { data, error } = await supabase.from("subjects").select("*").eq("sId", sId).single();
+
+ if (error) {
+ Alert.alert("Subject could not be fetched, please try again");
+ return;
+ }
+
+ SetSubject(data ?? null);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (sId) {
+ GetSubject(sId);
+ }
+ }, [sId])
+ );
+
+ const EditSubject = async () => {
+ if (!subject) return;
+
+ if(subject.title.trim() === '') {
+ Alert.alert("Title is required!");
+ return;
+ }
+
+ const { data, error: userError } = await supabase.auth.getUser();
+
+ if(userError || !data.user) {
+ router.replace("../createUser");
+ return;
+ }
+
+ SetIsSaving(true);
+
+ const { error: dbError } = await supabase.from("subjects").update({
+ title: subject.title,
+ description: subject.description,
+ isActive: subject.isActive,
+ lastChanged: new Date().toISOString(),
+ uId: data.user.id,
+ }).eq("sId", sId);
+
+ SetIsSaving(false);
+
+ if (dbError) {
+ Alert.alert("Subject could not be edited, please try again");
+ return;
+ }
+
+ Alert.alert("Subject successfully edited!");
+
+ router.back();
+ }
+
+ return (
+
+
+
+ {!subject && (
+
+ Subject not found
+
+ )}
+
+ {subject && (
+
+ Edit Subject
+
+
+
+ SetSubject(prev => prev ? { ...prev, title: text } : prev)}
+ />
+ SetSubject(prev => prev ? { ...prev, description: text } : prev)}
+ />
+ SetSubject(prev => prev ? { ...prev, isActive: !prev.isActive } : prev)}
+ style={defaultStyles.checkboxContainer}
+ >
+
+ {subject.isActive && ✓}
+
+ {subject.isActive ? 'Active' : 'inactive'}
+
+
+
+ {isSaving && (
+
+ )}
+ router.back()} />
+
+
+
+
+ )}
+
+ )
+}
+
diff --git a/app/subject/viewDetailsSubject.tsx b/app/subject/viewDetailsSubject.tsx
new file mode 100644
index 0000000..e13632a
--- /dev/null
+++ b/app/subject/viewDetailsSubject.tsx
@@ -0,0 +1,223 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { Session } from '@supabase/supabase-js';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useEffect, useState } from 'react';
+import { Alert, Button, Pressable, SectionList, Text, View } from "react-native";
+
+type Subject = {
+ sId: string;
+ title: string;
+ description: string;
+ isActive: boolean;
+ lastChanged: string;
+ uId: string;
+}
+
+type Assignment = {
+ aId: string;
+ title: string;
+ description: string;
+ deadline: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ sId: string;
+}
+
+export default function ViewDetailsSubject() {
+ const { sId } = useLocalSearchParams<{ sId: string }>();
+ const [subject, SetSubject] = useState(null)
+ const [assignments, SetAssignments] = useState([])
+ const [session, SetSession] = useState(null)
+
+ const assignmentSections = [
+ { title: "Upcoming Assignments", data: assignments.filter((assignment) => !assignment.isCompleted), emptyMessage: "No upcoming assignments" },
+ { title: "Completed Assignments", data: assignments.filter((assignment) => assignment.isCompleted), emptyMessage: "No completed assignments" },
+ ];
+
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null))
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession)
+ })
+ return () => sub.subscription.unsubscribe()
+ },
+ [])
+
+ const GetSubject = async (sId: string) => {
+ const { data, error } = await supabase.from("subjects").select("*").eq("sId", sId).single();
+
+ if (error) {
+ Alert.alert("Subject could not be fetched, please try again");
+ return;
+ }
+
+ SetSubject(data ?? null);
+ }
+
+ const GetAssignments = async (sId: string) => {
+ const { data, error } = await supabase.from("assignments").select("*").eq("sId", sId).order("deadline", { ascending: true });
+
+ if (error) {
+ Alert.alert("Assignments could not be fetched, please try again");
+ return;
+ }
+
+ SetAssignments(data ?? []);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session && sId) {
+ GetSubject(sId);
+ GetAssignments(sId);
+ }
+ }, [session, sId])
+ );
+
+ const DeleteSubject = async (sId: string) => {
+ Alert.alert(
+ "Delete Subject",
+ "Are you sure you want to delete this subject?",
+ [
+ {
+ text: "Cancel",
+ style: "cancel"
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ const { error } = await supabase.from("subjects").delete().eq("sId", sId);
+
+ if (error) {
+ Alert.alert("Subject could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Subject deleted successfully!");
+ router.back();
+ }
+ }
+ ]
+ )
+ }
+
+ const DeleteAssignment = async (aId: string, sId: string) => {
+ Alert.alert(
+ "Delete Assignment",
+ "Are you sure you want to delete this assignment?",
+ [
+ {
+ text: "Cancel",
+ style: "cancel"
+ },
+ {
+ text: "Delete",
+ style: "destructive",
+ onPress: async () => {
+ const { error } = await supabase.from("assignments").delete().eq("aId", aId);
+
+ if (error) {
+ Alert.alert("Assignment could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Assignment deleted successfully!");
+ GetAssignments(sId);
+ }
+ }
+ ]
+ )
+ }
+
+ return (
+
+ {
+ return (
+
+
+
+ )
+ },
+ headerRight: () => {
+ return (
+
+ await supabase.auth.signOut()} />
+
+ )
+ },
+ }}
+ />
+
+ {!subject && (
+
+ Subject not found
+
+ )}
+
+ {subject && (
+
+
+ {subject.title}
+ {subject.description}
+
+ {subject.isActive && ✓}
+
+ {subject.lastChanged}
+
+ router.push({pathname: "/subject/editSubject", params: { sId: subject.sId }})} />
+ DeleteSubject(subject.sId)} />
+
+
+ router.push({pathname: "/assignment/createAssignment", params: { sId: subject.sId }})} />
+
+
+
+ item.aId}
+ renderSectionHeader={({ section: { title } }) => {title}}
+ renderItem={({ item }) => {
+ const isOwner = session?.user.id === item.uId;
+
+ return (
+
+ router.push({pathname: "/assignment/viewDetailsAssignment", params: { aId: item.aId }})}>
+ {item.title}
+ {item.deadline}
+
+ {item.isCompleted && ✓}
+
+
+
+ {isOwner && (
+
+ router.push({pathname: "/assignment/editAssignment", params: { aId: item.aId }})} />
+ DeleteAssignment(item.aId, item.sId)} />
+
+ )}
+
+ );
+ }}
+ renderSectionFooter={({ section }) =>
+ section.data.length === 0 ? (
+
+ {section.emptyMessage}
+
+
+ ) : (
+
+ )
+ }
+ />
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/app/(tabs)/createTask.tsx b/app/task/createTask.tsx
similarity index 82%
rename from app/(tabs)/createTask.tsx
rename to app/task/createTask.tsx
index ed73b7a..af08418 100644
--- a/app/(tabs)/createTask.tsx
+++ b/app/task/createTask.tsx
@@ -1,19 +1,19 @@
import { defaultStyles } from '@/constants/defaultStyles';
import { supabase } from '@/lib/supabase';
-import { router, Stack } from 'expo-router';
+import { router, Stack, useLocalSearchParams } from 'expo-router';
import { useState } from 'react';
import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
export default function CreateTask() {
+ const aId = (useLocalSearchParams().aId as string) ?? null;
const [title, SetTitle] = useState('');
const [description, SetDescription] = useState('');
const [isCompleted, SetIsCompleted] = useState(false);
- const [deadline, SetDeadline] = useState('');
const [isSaving, SetIsSaving] = useState(false);
- const AddNote = async () => {
- if(title.trim() === '' || description.trim() === '' || deadline.trim() === '') {
- Alert.alert("All fields are required!");
+ const CreateTask = async () => {
+ if(title.trim() === '') {
+ Alert.alert("Title is required!");
return;
}
@@ -31,22 +31,20 @@ export default function CreateTask() {
description,
isCompleted,
lastChanged: new Date().toISOString(),
- deadline,
uId: data.user.id,
+ aId: aId,
});
if (dbError) {
Alert.alert("Task could not be created, please try again");
- SetIsSaving(false);
return;
}
- Alert.alert("Task successfully added!");
+ Alert.alert("Task successfully created!");
SetTitle('');
SetDescription('');
SetIsCompleted(false);
- SetDeadline('');
SetIsSaving(false);
@@ -79,12 +77,7 @@ export default function CreateTask() {
value={description}
onChangeText={SetDescription}
/>
-
+
SetIsCompleted(state => !state)}
style={defaultStyles.checkboxContainer}
@@ -95,7 +88,7 @@ export default function CreateTask() {
{isCompleted ? 'Completed' : 'Not completed'}
-
+
{isSaving && (
)}
diff --git a/app/task/editTask.tsx b/app/task/editTask.tsx
new file mode 100644
index 0000000..c21fc4f
--- /dev/null
+++ b/app/task/editTask.tsx
@@ -0,0 +1,135 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useState } from 'react';
+import { ActivityIndicator, Alert, Button, Keyboard, KeyboardAvoidingView, Platform, Pressable, Text, TextInput, TouchableWithoutFeedback, View } from 'react-native';
+
+type Task = {
+ tId: string;
+ title: string;
+ description: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ aId: string;
+}
+
+export default function EditTask() {
+ const { tId } = useLocalSearchParams<{ tId: string }>();
+ const [task, SetTask] = useState(null)
+ const [isSaving, SetIsSaving] = useState(false);
+
+ const GetTask = async (tId: string) => {
+ const { data, error } = await supabase.from("tasks").select("*").eq("tId", tId).single();
+
+ if (error) {
+ Alert.alert("Task could not be fetched, please try again");
+ return;
+ }
+
+ SetTask(data ?? null);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (tId) {
+ GetTask(tId);
+ }
+ }, [tId])
+ );
+
+ const EditTask = async () => {
+ if (!task) return;
+
+ if(task.title.trim() === '') {
+ Alert.alert("Title is required!");
+ return;
+ }
+
+ const { data, error: userError } = await supabase.auth.getUser();
+
+ if(userError || !data.user) {
+ router.replace("../createUser");
+ return;
+ }
+
+ SetIsSaving(true);
+
+ const { error: dbError } = await supabase.from("tasks").update({
+ title: task.title,
+ description: task.description,
+ isCompleted: task.isCompleted,
+ lastChanged: new Date().toISOString(),
+ uId: data.user.id,
+ aId: task.aId,
+ }).eq("tId", tId);
+
+ SetIsSaving(false);
+
+ if (dbError) {
+ Alert.alert("Task could not be edited, please try again");
+ return;
+ }
+
+ Alert.alert("Task successfully edited!");
+
+ router.back();
+ }
+
+ return (
+
+
+
+ {!task && (
+
+ Task not found
+
+ )}
+
+ {task && (
+
+ Edit Task
+
+
+
+ SetTask(prev => prev ? { ...prev, title: text } : prev)}
+ />
+ SetTask(prev => prev ? { ...prev, description: text } : prev)}
+ />
+ SetTask(prev => prev ? { ...prev, isCompleted: !prev.isCompleted } : prev)}
+ style={defaultStyles.checkboxContainer}
+ >
+
+ {task.isCompleted && ✓}
+
+ {task.isCompleted ? 'Completed' : 'Not Completed'}
+
+
+
+ {isSaving && (
+
+ )}
+ router.back()} />
+
+
+
+
+ )}
+
+ )
+}
+
diff --git a/app/task/viewDetailsTask.tsx b/app/task/viewDetailsTask.tsx
new file mode 100644
index 0000000..049c933
--- /dev/null
+++ b/app/task/viewDetailsTask.tsx
@@ -0,0 +1,125 @@
+import { defaultStyles } from '@/constants/defaultStyles';
+import { supabase } from '@/lib/supabase';
+import { Session } from '@supabase/supabase-js';
+import { router, Stack, useFocusEffect, useLocalSearchParams } from 'expo-router';
+import { useCallback, useEffect, useState } from 'react';
+import { Alert, Button, Text, View } from "react-native";
+
+type Task = {
+ tId: string;
+ title: string;
+ description: string;
+ isCompleted: boolean;
+ lastChanged: string;
+ uId: string;
+ aId: string;
+}
+
+export default function ViewDetailsTask() {
+ const { tId } = useLocalSearchParams<{ tId: string }>();
+ const [task, SetTask] = useState(null)
+ const [session, SetSession] = useState(null)
+
+ useEffect(() => {
+ supabase.auth.getSession().then(({ data }) => SetSession(data.session ?? null))
+ const { data: sub } = supabase.auth.onAuthStateChange((_event, newSession) => {
+ SetSession(newSession)
+ })
+ return () => sub.subscription.unsubscribe()
+ },
+ [])
+
+ const GetTask = async (tId: string) => {
+ const { data, error } = await supabase.from("tasks").select("*").eq("tId", tId).single();
+
+ if (error) {
+ Alert.alert("Task could not be fetched, please try again");
+ return;
+ }
+
+ SetTask(data ?? null);
+ }
+
+ useFocusEffect(
+ useCallback(() => {
+ if (session && tId) {
+ GetTask(tId);
+ }
+ }, [session, tId])
+ );
+
+ const DeleteTask = async (tId: 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", tId);
+
+ if (error) {
+ Alert.alert("Task could not be deleted, please try again");
+ return;
+ }
+
+ Alert.alert("Task deleted successfully!");
+ router.back();
+ }
+ }
+ ]
+ )
+ }
+
+ return (
+
+ {
+ return (
+
+
+
+ )
+ },
+ headerRight: () => {
+ return (
+
+ await supabase.auth.signOut()} />
+
+ )
+ },
+ }}
+ />
+
+ {!task && (
+
+ Task not found
+
+ )}
+
+ {task && (
+
+ {task.title}
+ {task.description}
+
+ {task.isCompleted && ✓}
+
+ {task.lastChanged}
+
+
+ router.push({pathname: "/task/editTask", params: { tId: task.tId }})} />
+ DeleteTask(task.tId)} />
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3e3b197..54eb35d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"expo-image": "~3.0.11",
"expo-linking": "~8.0.11",
"expo-router": "~6.0.23",
- "expo-secure-store": "^55.0.13",
+ "expo-secure-store": "~15.0.8",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"expo-symbols": "~1.0.8",
@@ -6554,9 +6554,9 @@
}
},
"node_modules/expo-secure-store": {
- "version": "55.0.13",
- "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-55.0.13.tgz",
- "integrity": "sha512-I6r0JNO1Fd4o0Gu7Ixiic7s89lqgdUHq17uBH9y1f/AntoyKn71TdtYJH82RgfsBbu5qNVzrwImmvlANyOlITQ==",
+ "version": "15.0.8",
+ "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-15.0.8.tgz",
+ "integrity": "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
diff --git a/package.json b/package.json
index 1e5bbbe..ea000b3 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,7 @@
"expo-image": "~3.0.11",
"expo-linking": "~8.0.11",
"expo-router": "~6.0.23",
- "expo-secure-store": "^55.0.13",
+ "expo-secure-store": "~15.0.8",
"expo-splash-screen": "~31.0.13",
"expo-status-bar": "~3.0.9",
"expo-symbols": "~1.0.8",