Fixed infinite loop on the dashboards
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const websiteUrl = process.env.NODE_ENV === 'production' ? "https://encoach.com" : "http://localhost:3000";
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
reactStrictMode: false,
|
||||
output: "standalone",
|
||||
async headers() {
|
||||
return [
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@simbathesailor/use-what-changed": "^2.0.0",
|
||||
"@types/blob-stream": "^0.1.33",
|
||||
"@types/formidable": "^3.4.0",
|
||||
"@types/howler": "^2.2.11",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Modal from "@/components/Modal";
|
||||
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
|
||||
import useUsers, { userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers";
|
||||
import {CorporateUser, Group, MasterCorporateUser, Stat, User } from "@/interfaces/user";
|
||||
import UserList from "@/pages/(admin)/Lists/UserList";
|
||||
import {dateSorter} from "@/utils";
|
||||
import moment from "moment";
|
||||
@@ -156,6 +156,7 @@ const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanc
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
@@ -165,8 +166,8 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
|
||||
const {balance} = useUserBalance();
|
||||
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers({type: "teacher"});
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent);
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(userHashTeacher);
|
||||
|
||||
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useMemo} from "react";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import useUsers, { userHashStudent, userHashTeacher, userHashCorporate } from "@/hooks/useUsers";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import {User} from "@/interfaces/user";
|
||||
import Select from "@/components/Low/Select";
|
||||
@@ -63,8 +63,8 @@ const Card = ({user}: {user: User}) => {
|
||||
const CorporateStudentsLevels = () => {
|
||||
const [corporateId, setCorporateId] = React.useState<string>("");
|
||||
|
||||
const {users: students} = useUsers({type: "student"});
|
||||
const {users: corporates} = useUsers({type: "corporate"});
|
||||
const {users: students} = useUsers(userHashStudent);
|
||||
const {users: corporates} = useUsers(userHashCorporate);
|
||||
|
||||
const corporate = useMemo(() => corporates.find((u) => u.id === corporateId) || corporates[0], [corporates, corporateId]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Modal from "@/components/Modal";
|
||||
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import useUsers, { userHashStudent, userHashTeacher, userHashCorporate } from "@/hooks/useUsers";
|
||||
import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
|
||||
import UserList from "@/pages/(admin)/Lists/UserList";
|
||||
import {dateSorter} from "@/utils";
|
||||
@@ -302,9 +302,9 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
|
||||
const {data: stats} = useFilterRecordsByUser<Stat[]>();
|
||||
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers({type: "teacher"});
|
||||
const {users: corporates, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers({type: "corporate"});
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent);
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(userHashTeacher);
|
||||
const {users: corporates, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers(userHashCorporate);
|
||||
|
||||
const {groups} = useGroups({admin: user.id, userType: user.type});
|
||||
const {balance} = useUserBalance();
|
||||
|
||||
@@ -6,7 +6,7 @@ import useAssignments from "@/hooks/useAssignments";
|
||||
import useGradingSystem from "@/hooks/useGrading";
|
||||
import useInvites from "@/hooks/useInvites";
|
||||
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import useUsers, { userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers";
|
||||
import {Invite} from "@/interfaces/invite";
|
||||
import {Assignment} from "@/interfaces/results";
|
||||
import {CorporateUser, MasterCorporateUser, Stat, User} from "@/interfaces/user";
|
||||
@@ -43,8 +43,8 @@ export default function StudentDashboard({user, linkedCorporate}: Props) {
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assignees: user?.id});
|
||||
const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user.id});
|
||||
|
||||
const {users: teachers} = useUsers({type: "teacher"});
|
||||
const {users: corporates} = useUsers({type: "corporate"});
|
||||
const {users: teachers} = useUsers(userHashTeacher);
|
||||
const {users: corporates} = useUsers(userHashCorporate);
|
||||
|
||||
const users = useMemo(() => [...teachers, ...corporates], [teachers, corporates]);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Modal from "@/components/Modal";
|
||||
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import useUsers, { userHashStudent, userHashTeacher, userHashCorporate } from "@/hooks/useUsers";
|
||||
import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
|
||||
import UserList from "@/pages/(admin)/Lists/UserList";
|
||||
import {dateSorter} from "@/utils";
|
||||
@@ -67,7 +67,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
const {permissions} = usePermissions(user.id);
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
|
||||
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent);
|
||||
|
||||
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -2,10 +2,13 @@ import {Type, User} from "@/interfaces/user";
|
||||
import Axios from "axios";
|
||||
import {useEffect, useState} from "react";
|
||||
import {setupCache} from "axios-cache-interceptor";
|
||||
|
||||
const instance = Axios.create();
|
||||
const axios = setupCache(instance);
|
||||
|
||||
export const userHashStudent = { type: "student" } as { type: Type };
|
||||
export const userHashTeacher = { type: "teacher" } as { type: Type };
|
||||
export const userHashCorporate = { type: "corporate" } as { type: Type };
|
||||
|
||||
export default function useUsers(props?: {type?: Type; page?: number; size?: number}) {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
@@ -9,7 +9,7 @@ import axios from "axios";
|
||||
import clsx from "clsx";
|
||||
import {capitalize, reverse} from "lodash";
|
||||
import moment from "moment";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {Fragment, useEffect, useState, useMemo} from "react";
|
||||
import {BsArrowDown, BsArrowDownUp, BsArrowUp, BsCheck, BsCheckCircle, BsEye, BsFillExclamationOctagonFill, BsPerson, BsTrash} from "react-icons/bs";
|
||||
import {toast} from "react-toastify";
|
||||
import {countries, TCountries} from "countries-list";
|
||||
@@ -59,7 +59,12 @@ export default function UserList({
|
||||
const [displayUsers, setDisplayUsers] = useState<User[]>([]);
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
|
||||
const {users, page, total, reload, next, previous} = useUsers({type, size: 25});
|
||||
const userHash = useMemo(() => ({
|
||||
type,
|
||||
size: 25,
|
||||
}), [type])
|
||||
|
||||
const {users, page, total, reload, next, previous} = useUsers(userHash);
|
||||
const {permissions} = usePermissions(user?.id || "");
|
||||
const {balance} = useUserBalance();
|
||||
const {groups} = useGroups({
|
||||
|
||||
@@ -1,30 +1,41 @@
|
||||
import {CorporateUser, Group, User, Type} from "@/interfaces/user";
|
||||
import { CorporateUser, Group, User, Type } from "@/interfaces/user";
|
||||
import axios from "axios";
|
||||
|
||||
export const isUserFromCorporate = async (userID: string) => {
|
||||
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`)).data;
|
||||
const users = (await axios.get<User[]>("/api/users/list")).data;
|
||||
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`))
|
||||
.data;
|
||||
const usersData = (await axios.get<{users: User[], total: number}>("/api/users/list")).data;
|
||||
|
||||
const adminTypes = groups.map((g) => users.find((u) => u.id === g.admin)?.type);
|
||||
const adminTypes = groups.reduce((accm: Type[], g) => {
|
||||
const user = usersData.users.find((u) => u.id === g.admin);
|
||||
if (user) {
|
||||
return [...accm, user.type];
|
||||
}
|
||||
|
||||
return accm;
|
||||
}, []);
|
||||
return adminTypes.includes("corporate");
|
||||
};
|
||||
|
||||
const getAdminForGroup = async (userID: string, role: Type) => {
|
||||
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`)).data;
|
||||
const groups = (await axios.get<Group[]>(`/api/groups?participant=${userID}`))
|
||||
.data;
|
||||
|
||||
const adminRequests = await Promise.all(
|
||||
groups.map(async (g) => {
|
||||
const userRequest = await axios.get<User>(`/api/users/${g.admin}`);
|
||||
if (userRequest.status === 200) return userRequest.data;
|
||||
return undefined;
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const admins = adminRequests.filter((x) => x?.type === role);
|
||||
return admins.length > 0 ? (admins[0] as CorporateUser) : undefined;
|
||||
};
|
||||
|
||||
export const getUserCorporate = async (userID: string): Promise<CorporateUser | undefined> => {
|
||||
export const getUserCorporate = async (
|
||||
userID: string
|
||||
): Promise<CorporateUser | undefined> => {
|
||||
const userRequest = await axios.get<User>(`/api/users/${userID}`);
|
||||
if (userRequest.status === 200) {
|
||||
const user = userRequest.data;
|
||||
|
||||
@@ -86,6 +86,9 @@ export async function getLinkedUsers(userID?: string, userType?: Type, type?: Ty
|
||||
...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []),
|
||||
]);
|
||||
|
||||
// ⨯ [FirebaseError: Invalid Query. A non-empty array is required for 'in' filters.] {
|
||||
if(participants.length === 0) return {users: [], total: 0};
|
||||
|
||||
const snapshot = await getDocs(query(collection(db, "users"), ...[where(documentId(), "in", participants), ...q]));
|
||||
const users = snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
|
||||
37
yarn.lock
37
yarn.lock
@@ -1592,6 +1592,11 @@
|
||||
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz"
|
||||
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
|
||||
|
||||
"@simbathesailor/use-what-changed@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@simbathesailor/use-what-changed/-/use-what-changed-2.0.0.tgz#7f82d78f92c8588b5fadd702065dde93bd781403"
|
||||
integrity sha512-ulBNrPSvfho9UN6zS2fii3AsdEcp2fMaKeqUZZeCNPaZbB6aXyTUhpEN9atjMAbu/eyK3AY8L4SYJUG62Ekocw==
|
||||
|
||||
"@swc/counter@^0.1.3":
|
||||
version "0.1.3"
|
||||
resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz"
|
||||
@@ -6450,7 +6455,16 @@ streamsearch@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -6514,7 +6528,14 @@ string_decoder@~1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -7102,8 +7123,7 @@ wordwrap@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
|
||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
name wrap-ansi-cjs
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
@@ -7121,6 +7141,15 @@ wrap-ansi@^6.2.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user