From 81bead43ff7b090b13cb100864830c44a3c86dda Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 22 Sep 2023 22:55:36 +0800 Subject: [PATCH 1/6] Fixed filtering for study groups --- .../ParseStudyGroupList.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx b/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx index 93c70cb..fdd1ddc 100644 --- a/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx +++ b/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx @@ -17,7 +17,8 @@ export default function ParseStudyGroupList( let result: any[] = []; // We first remove any instances that do not have a study group associated with it let data_filtered = data.filter( - (item: StudentStatusFilterType) => item.study_group !== "" + (item: StudentStatusFilterType) => + item.study_group !== undefined && item.study_group.length > 0 ); // console.log("Filtered Data:", data_filtered); // Then we flatten the data so that all attributes are in the first layer @@ -35,19 +36,20 @@ export default function ParseStudyGroupList( })); // console.log("Flattened Data:", data_flattened); - // We take from the array all unique subject names - let unique_subjects = [ + // We take from the array all unique study groups + let unique_studygroups = [ ...new Set( - data_flattened.map((item: StudentStatusFilterType) => item.subject) + data_flattened.map((item: StudentStatusFilterType) => item.study_group) ), ]; // Then we create arrays unique to each subject - unique_subjects.forEach((subject, index: number) => { + unique_studygroups.forEach((studygroup, index: number) => { // We build another array for each subject, including only those instances that are the same subject name let unique_subject_list = data_flattened .filter( - (item: StudentStatusFilterTypeFlattened) => item.subject === subject + (item: StudentStatusFilterTypeFlattened) => + item.study_group === studygroup ) .map((item: StudentStatusFilterTypeFlattened) => ({ active: item.active, @@ -147,6 +149,7 @@ export default function ParseStudyGroupList( // We now build the object that we will return const subjectUserMap: subjectUserMapType = { subject: "", + study_group: "", users: [], latitude: 0, longitude: 0, @@ -156,6 +159,9 @@ export default function ParseStudyGroupList( if (!subjectUserMap["users"]) { subjectUserMap["users"] = []; } + if (!subjectUserMap["study_group"]) { + subjectUserMap["study_group"] = unique_subject_list[0].study_group; + } subjectUserMap["subject"] = item.subject; subjectUserMap["latitude"] = avgLat; subjectUserMap["longitude"] = avgLng; @@ -167,7 +173,7 @@ export default function ParseStudyGroupList( result = result.concat([subjectUserMap]); }); - // console.log("Final Result:", result); + console.log("Final Result:", result); return result; } From c4413a185d8b01107a3c6bb3a0772a18f831d35c Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 22 Sep 2023 23:08:06 +0800 Subject: [PATCH 2/6] Hidden unused console.log --- .../ParseStudentStatusList/ParseStudentStatusList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx b/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx index 4e673c9..1f0b16e 100644 --- a/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx +++ b/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx @@ -16,7 +16,7 @@ export default function ParseStudentStatusList(data: any) { let data_filtered = data.filter( (item: StudentStatusFilterType) => item.study_group == "" ); - console.log("Filtered Data:", data_filtered); + // console.log("Filtered Data:", data_filtered); // Then we flatten the data so that all attributes are in the first layer // We first flatten the data to remove nested entries let data_flattened = data_filtered.map((item: StudentStatusFilterType) => ({ From 7da7d0f2175ff03ac1c741c0f4dcb9d000486278 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 22 Sep 2023 23:08:20 +0800 Subject: [PATCH 3/6] Hidden unused console.log in api --- src/components/Api/Api.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx index 74b86d5..9d9f826 100644 --- a/src/components/Api/Api.tsx +++ b/src/components/Api/Api.tsx @@ -246,7 +246,6 @@ export async function GetStudentStatus() { export async function PatchStudentStatus(info: StudentStatusPatchType) { const config = await GetConfig(); - console.log(info); return instance .patch("/api/v1/student_status/self/", info, config) .then((response) => { From 19d19c3dd5ccece39cbb7a93f83d1bb40e7cbd8c Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 22 Sep 2023 23:08:54 +0800 Subject: [PATCH 4/6] Reflect user pin location change if user manually overrides location --- src/routes/Home/Home.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx index 3a0317a..10f15ca 100644 --- a/src/routes/Home/Home.tsx +++ b/src/routes/Home/Home.tsx @@ -243,8 +243,12 @@ export default function Home() { minZoomLevel={19} zoomTapEnabled initialRegion={{ - latitude: location.coords.latitude, - longitude: location.coords.longitude, + latitude: + student_status?.location?.latitude || + location.coords.latitude, + longitude: + student_status?.location?.longitude || + location.coords.longitude, latitudeDelta: 0.4, longitudeDelta: 0.4, }} @@ -372,11 +376,16 @@ export default function Home() { } )} { const newLocation = e.nativeEvent.coordinate; const distance = GetDistance( From 1bd07f9edd83964b260cc6d3907e750bb383ca42 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 24 Sep 2023 21:02:34 +0800 Subject: [PATCH 5/6] Optimized homepage rendering and removed overly complicated components --- src/components/Api/Api.tsx | 14 ++ .../ParseStudentStatusList.tsx | 35 ---- .../ParseStudyGroupList.tsx | 179 ----------------- src/interfaces/Interfaces.tsx | 11 + src/routes/Home/Home.tsx | 188 ++++++++++-------- 5 files changed, 129 insertions(+), 298 deletions(-) delete mode 100644 src/components/ParseStudentStatusList/ParseStudentStatusList.tsx delete mode 100644 src/components/ParseStudyGroupList/ParseStudyGroupList.tsx diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx index 9d9f826..195b8cf 100644 --- a/src/components/Api/Api.tsx +++ b/src/components/Api/Api.tsx @@ -296,3 +296,17 @@ export async function GetStudentStatusListFilteredCurrentLocation() { return [false, error_message]; }); } + +export async function GetStudyGroupListFiltered() { + const config = await GetConfig(); + return instance + .get("/api/v1/study_groups/near/", config) + .then((response) => { + console.log("Data:", response.data); + return [true, response.data]; + }) + .catch((error) => { + let error_message = ParseError(error); + return [false, error_message]; + }); +} diff --git a/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx b/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx deleted file mode 100644 index 1f0b16e..0000000 --- a/src/components/ParseStudentStatusList/ParseStudentStatusList.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from "react"; -import { View, Text } from "react-native"; -import { - StudentStatusFilterType, - LocationType, - subjectUserMapType, - StudentStatusListType, - StudentStatusFilterTypeFlattened, -} from "../../interfaces/Interfaces"; -import { Double, Float } from "react-native/Libraries/Types/CodegenTypes"; - -export default function ParseStudentStatusList(data: any) { - // Individual map point generation for student statuses - // Include only those that do not have study groups - // Then we simply flatten the data. Much simpler compared to study groups - let data_filtered = data.filter( - (item: StudentStatusFilterType) => item.study_group == "" - ); - // console.log("Filtered Data:", data_filtered); - // Then we flatten the data so that all attributes are in the first layer - // We first flatten the data to remove nested entries - let data_flattened = data_filtered.map((item: StudentStatusFilterType) => ({ - active: item.active, - distance: item.distance, - landmark: item.landmark, - latitude: item.location.latitude, - longitude: item.location.longitude, - study_group: item.study_group, - subject: item.subject, - user: item.user, - weight: 1, - })); - - return data_flattened; -} diff --git a/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx b/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx deleted file mode 100644 index fdd1ddc..0000000 --- a/src/components/ParseStudyGroupList/ParseStudyGroupList.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import * as React from "react"; -import { View, Text } from "react-native"; -import { - StudentStatusFilterType, - LocationType, - subjectUserMapType, - StudentStatusListType, - StudentStatusFilterTypeFlattened, -} from "../../interfaces/Interfaces"; -import { Double, Float } from "react-native/Libraries/Types/CodegenTypes"; - -export default function ParseStudyGroupList( - data: any, - user_location: LocationType -) { - // Circle generation for students in a study group - let result: any[] = []; - // We first remove any instances that do not have a study group associated with it - let data_filtered = data.filter( - (item: StudentStatusFilterType) => - item.study_group !== undefined && item.study_group.length > 0 - ); - // console.log("Filtered Data:", data_filtered); - // Then we flatten the data so that all attributes are in the first layer - // We first flatten the data to remove nested entries - let data_flattened = data_filtered.map((item: StudentStatusFilterType) => ({ - active: item.active, - distance: item.distance, - landmark: item.landmark, - latitude: item.location.latitude, - longitude: item.location.longitude, - study_group: item.study_group, - subject: item.subject, - user: item.user, - weight: 1, - })); - // console.log("Flattened Data:", data_flattened); - - // We take from the array all unique study groups - let unique_studygroups = [ - ...new Set( - data_flattened.map((item: StudentStatusFilterType) => item.study_group) - ), - ]; - - // Then we create arrays unique to each subject - unique_studygroups.forEach((studygroup, index: number) => { - // We build another array for each subject, including only those instances that are the same subject name - let unique_subject_list = data_flattened - .filter( - (item: StudentStatusFilterTypeFlattened) => - item.study_group === studygroup - ) - .map((item: StudentStatusFilterTypeFlattened) => ({ - active: item.active, - distance: item.distance, - landmark: item.landmark, - latitude: item.latitude, - longitude: item.longitude, - study_group: item.study_group, - subject: item.subject, - user: item.user, - weight: 1, - })); - - /* - let unique_subject_object = data_flattened.filter( - (item: StudentStatusFilterTypeFlattened) => item.subject === subject - ); - */ - - // We get the circle's center by averaging all the points - // Calculate the average latitude and longitude - const totalLat = unique_subject_list.reduce( - (sum: Double, point: LocationType) => sum + point.latitude, - 0 - ); - const totalLng = unique_subject_list.reduce( - (sum: Double, point: LocationType) => sum + point.longitude, - 0 - ); - - let avgLat = totalLat / unique_subject_list.length; - let avgLng = totalLng / unique_subject_list.length; - - // console.log("Center Latitude:", avgLat); - // console.log("Center Longitude:", avgLng); - - // Haversine Distance Function - function haversineDistance( - lat1: number, - lon1: number, - lat2: number, - lon2: number - ) { - function toRad(x: number) { - return (x * Math.PI) / 180; - } - - lat1 = toRad(lat1); - lon1 = toRad(lon1); - lat2 = toRad(lat2); - lon2 = toRad(lon2); - - let dLat = lat2 - lat1; - let dLon = lon2 - lon1; - - let a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(lat1) * - Math.cos(lat2) * - Math.sin(dLon / 2) * - Math.sin(dLon / 2); - let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - // Multiply by Earth's radius (in kilometers) to obtain distance - let distance = 6371 * c; - - // Convert to meters - return distance * 1000; - } - - // We now calculate the radius of the circle using the Haversine Distance Formula - // For each entry, we calculate the Haversine Distance from the user's location. - // The largest value is used as the circle radius - - let circle_radius = Math.max( - ...unique_subject_list.map( - (item: StudentStatusFilterTypeFlattened, index: number) => { - let distance = haversineDistance( - item.latitude, - item.longitude, - user_location.latitude, - user_location.longitude - ); - - /*console.log( - "Haversine Distance for entry #", - index + 1, - ":", - distance - );*/ - return distance; - } - ) - ); - // console.log("Radius:", circle_radius); - - // We now build the object that we will return - const subjectUserMap: subjectUserMapType = { - subject: "", - study_group: "", - users: [], - latitude: 0, - longitude: 0, - radius: 0, - }; - unique_subject_list.forEach((item: StudentStatusFilterType) => { - if (!subjectUserMap["users"]) { - subjectUserMap["users"] = []; - } - if (!subjectUserMap["study_group"]) { - subjectUserMap["study_group"] = unique_subject_list[0].study_group; - } - subjectUserMap["subject"] = item.subject; - subjectUserMap["latitude"] = avgLat; - subjectUserMap["longitude"] = avgLng; - subjectUserMap["radius"] = circle_radius; - subjectUserMap["users"].push(item.user); - }); - console.log(subjectUserMap); - - result = result.concat([subjectUserMap]); - }); - - console.log("Final Result:", result); - - return result; -} diff --git a/src/interfaces/Interfaces.tsx b/src/interfaces/Interfaces.tsx index 7623b88..be6d983 100644 --- a/src/interfaces/Interfaces.tsx +++ b/src/interfaces/Interfaces.tsx @@ -166,6 +166,17 @@ export interface StudentStatusFilterTypeFlattened { weight?: number; } +export interface StudyGroupType { + name: string; + users: string[]; + distance: number; + landmark: string | null; + location: LocationType; + subject: string; + radius: number; +} +export type StudyGroupReturnType = [boolean, StudyGroupType[]]; + export type StudentStatusReturnType = [boolean, StudentStatusType]; export type StudentStatusListType = Array; diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx index 10f15ca..7e19e6e 100644 --- a/src/routes/Home/Home.tsx +++ b/src/routes/Home/Home.tsx @@ -24,20 +24,22 @@ import { subjectUserMapType, StudentStatusFilterTypeFlattened, StudentStatusPatchType, + StudyGroupType, + StudyGroupReturnType, + StudentStatusFilterType, } from "../../interfaces/Interfaces"; import { useNavigation } from "@react-navigation/native"; import { GetStudentStatus, GetStudentStatusList, GetStudentStatusListFiltered, + GetStudyGroupListFiltered, PatchStudentStatus, urlProvider, } from "../../components/Api/Api"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useToast } from "react-native-toast-notifications"; import React from "react"; -import ParseStudyGroupList from "../../components/ParseStudyGroupList/ParseStudyGroupList"; -import ParseStudentStatusList from "../../components/ParseStudentStatusList/ParseStudentStatusList"; import CustomMapCallout from "../../components/CustomMapCallout/CustomMapCallout"; import MapRendererFar from "../../components/MapRenderer/MapRendererFar"; import GetDistanceFromUSTP from "../../components/GetDistance/GetDistanceFromUSTP"; @@ -120,7 +122,7 @@ export default function Home() { const [subject, setSubject] = useState(""); const [buttonLabel, setButtonLabel] = useState("Start studying"); const [student_status, setStudentStatus] = useState(); - const StudentStatus = useQuery({ + const StudentStatusQuery = useQuery({ queryKey: ["user_status"], queryFn: async () => { const data = await GetStudentStatus(); @@ -182,12 +184,10 @@ export default function Home() { }, }); - const [student_statuses, setStudentStatuses] = useState< - StudentStatusFilterTypeFlattened[] - >([]); - const [study_groups, setStudyGroups] = useState([]); + const [student_statuses, setStudentStatuses] = + useState([]); // Student Status List - const StudentStatusList = useQuery({ + const StudentStatusListQuery = useQuery({ enabled: studying, queryKey: ["user_status_list"], queryFn: async () => { @@ -199,13 +199,38 @@ export default function Home() { }, onSuccess: (data: StudentStatusListReturnType) => { if (data[1] && location) { - setStudyGroups( - ParseStudyGroupList(data[1], { - latitude: location.coords.latitude, - longitude: location.coords.longitude, - }) + // Filter to only include students studying solo + let data_filtered = data[1].filter( + (item: StudentStatusFilterType) => item.study_group == "" ); - setStudentStatuses(ParseStudentStatusList(data[1])); + setStudentStatuses(data_filtered); + } + }, + onError: (error: Error) => { + toast.show(String(error), { + type: "warning", + placement: "top", + duration: 2000, + animationType: "slide-in", + }); + }, + }); + + const [study_groups, setStudyGroups] = useState([]); + // Student Status List + const StudyGroupQuery = useQuery({ + enabled: studying, + queryKey: ["study_group_list"], + queryFn: async () => { + const data = await GetStudyGroupListFiltered(); + if (data[0] == false) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data: StudyGroupReturnType) => { + if (data[1] && location) { + setStudyGroups(data[1]); } }, onError: (error: Error) => { @@ -255,10 +280,7 @@ export default function Home() { loadingBackgroundColor={colors.secondary_2} > {student_statuses.map( - ( - student_status: StudentStatusFilterTypeFlattened, - index: number - ) => { + (student_status: StudentStatusFilterType, index: number) => { const randomColorWithOpacity = `rgba(${Math.floor( Math.random() * 256 )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( @@ -268,7 +290,7 @@ export default function Home() { return ( { @@ -305,76 +327,74 @@ export default function Home() { ); } )} - {study_groups.map( - (student_status: subjectUserMapType, index: number) => { - const randomColorWithOpacity = `rgba(${Math.floor( - Math.random() * 256 - )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( - Math.random() * 256 - )}, 0.7)`; + {study_groups.map((studygroup: StudyGroupType, index: number) => { + const randomColorWithOpacity = `rgba(${Math.floor( + Math.random() * 256 + )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( + Math.random() * 256 + )}, 0.7)`; - return ( - - { - toast.hideAll(); - toast.show( - + { + toast.hideAll(); + toast.show( + + + Subject: {studygroup.subject} + + + Students Studying: {studygroup.users.length} + + - , - { - type: "normal", - placement: "top", - duration: 2000, - animationType: "slide-in", - style: { - backgroundColor: colors.secondary_2, - borderWidth: 1, - borderColor: colors.primary_1, - }, - } - ); - }} - /> - - - ); - } - )} + + , + { + type: "normal", + placement: "top", + duration: 2000, + animationType: "slide-in", + style: { + backgroundColor: colors.secondary_2, + borderWidth: 1, + borderColor: colors.primary_1, + }, + } + ); + }} + /> + + + ); + })} Date: Sun, 24 Sep 2023 21:26:15 +0800 Subject: [PATCH 6/6] Added global study groups rendering --- src/components/Api/Api.tsx | 15 +++++- src/routes/Home/Home.tsx | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx index 195b8cf..430124f 100644 --- a/src/components/Api/Api.tsx +++ b/src/components/Api/Api.tsx @@ -302,7 +302,20 @@ export async function GetStudyGroupListFiltered() { return instance .get("/api/v1/study_groups/near/", config) .then((response) => { - console.log("Data:", response.data); + return [true, response.data]; + }) + .catch((error) => { + let error_message = ParseError(error); + return [false, error_message]; + }); +} + +export async function GetStudyGroupList() { + const config = await GetConfig(); + return instance + .get("/api/v1/study_groups/", config) + .then((response) => { + console.log("test", response.data); return [true, response.data]; }) .catch((error) => { diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx index 7e19e6e..f926c26 100644 --- a/src/routes/Home/Home.tsx +++ b/src/routes/Home/Home.tsx @@ -33,6 +33,7 @@ import { GetStudentStatus, GetStudentStatusList, GetStudentStatusListFiltered, + GetStudyGroupList, GetStudyGroupListFiltered, PatchStudentStatus, urlProvider, @@ -242,6 +243,34 @@ export default function Home() { }); }, }); + const [study_groups_global, setStudyGroupsGlobal] = useState< + StudyGroupType[] + >([]); + // Student Status List + const StudyGroupGlobalQuery = useQuery({ + enabled: !studying, + queryKey: ["study_group_list_global"], + queryFn: async () => { + const data = await GetStudyGroupList(); + if (data[0] == false) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data: StudyGroupReturnType) => { + if (data[1] && location) { + setStudyGroupsGlobal(data[1]); + } + }, + onError: (error: Error) => { + toast.show(String(error), { + type: "warning", + placement: "top", + duration: 2000, + animationType: "slide-in", + }); + }, + }); function CustomMap() { if (dist && location) { @@ -395,6 +424,76 @@ export default function Home() { ); })} + {study_groups_global.map( + (studygroup: StudyGroupType, index: number) => { + const randomColorWithOpacity = `rgba(${Math.floor( + Math.random() * 256 + )}, ${Math.floor(Math.random() * 256)}, ${Math.floor( + Math.random() * 256 + )}, 0.7)`; + + return ( + + { + toast.hideAll(); + toast.show( + + + Subject: {studygroup.subject} + + + Students Studying: {studygroup.users.length} + + + , + { + type: "normal", + placement: "top", + duration: 2000, + animationType: "slide-in", + style: { + backgroundColor: colors.secondary_2, + borderWidth: 1, + borderColor: colors.primary_1, + }, + } + ); + }} + /> + + + ); + } + )}