Merged with develop
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import useEntities from "@/hooks/useEntities";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import {User} from "@/interfaces/user";
|
||||
import { User } from "@/interfaces/user";
|
||||
import clsx from "clsx";
|
||||
import {useRouter} from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import Navbar from "../Navbar";
|
||||
import Sidebar from "../Sidebar";
|
||||
@@ -23,19 +23,19 @@ export default function Layout({
|
||||
user,
|
||||
children,
|
||||
className,
|
||||
bgColor="bg-white",
|
||||
bgColor = "bg-white",
|
||||
hideSidebar,
|
||||
navDisabled = false,
|
||||
focusMode = false,
|
||||
onFocusLayerMouseEnter
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const {entities} = useEntities()
|
||||
const { entities } = useEntities()
|
||||
|
||||
return (
|
||||
<main className={clsx("w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke relative")}>
|
||||
<ToastContainer />
|
||||
{!hideSidebar && (
|
||||
{!hideSidebar && user && (
|
||||
<Navbar
|
||||
path={router.pathname}
|
||||
user={user}
|
||||
@@ -45,7 +45,7 @@ export default function Layout({
|
||||
/>
|
||||
)}
|
||||
<div className={clsx("h-full w-full flex gap-2")}>
|
||||
{!hideSidebar && (
|
||||
{!hideSidebar && user && (
|
||||
<Sidebar
|
||||
path={router.pathname}
|
||||
navDisabled={navDisabled}
|
||||
|
||||
@@ -13,6 +13,7 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
max?: number;
|
||||
min?: number;
|
||||
thin?: boolean
|
||||
name: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
@@ -29,6 +30,7 @@ export default function Input({
|
||||
className,
|
||||
roundness = "full",
|
||||
disabled = false,
|
||||
thin = false,
|
||||
min,
|
||||
onChange,
|
||||
}: Props) {
|
||||
@@ -95,9 +97,10 @@ export default function Input({
|
||||
min={type === "number" ? (min ?? 0) : undefined}
|
||||
placeholder={placeholder}
|
||||
className={clsx(
|
||||
"px-8 py-6 text-sm font-normal bg-white border border-mti-gray-platinum focus:outline-none",
|
||||
"px-8 text-sm font-normal bg-white border border-mti-gray-platinum focus:outline-none",
|
||||
"placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed",
|
||||
roundness === "full" ? "rounded-full" : "rounded-xl",
|
||||
thin ? 'py-4' : 'py-6'
|
||||
)}
|
||||
required={required}
|
||||
defaultValue={defaultValue}
|
||||
|
||||
@@ -8,163 +8,166 @@ import useGroups from "@/hooks/useGroups";
|
||||
import useRecordStore from "@/stores/recordStore";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import { mapBy } from "@/utils";
|
||||
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
|
||||
|
||||
|
||||
type TimeFilter = "months" | "weeks" | "days";
|
||||
type Filter = TimeFilter | "assignments" | undefined;
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
entities: EntityWithRoles[]
|
||||
users: User[]
|
||||
filterState: {
|
||||
filter: Filter,
|
||||
setFilter: React.Dispatch<React.SetStateAction<Filter>>
|
||||
},
|
||||
assignments?: boolean;
|
||||
children?: ReactNode
|
||||
user: User;
|
||||
entities: EntityWithRoles[]
|
||||
users: User[]
|
||||
filterState: {
|
||||
filter: Filter,
|
||||
setFilter: React.Dispatch<React.SetStateAction<Filter>>
|
||||
},
|
||||
assignments?: boolean;
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const defaultSelectableCorporate = {
|
||||
value: "",
|
||||
label: "All",
|
||||
value: "",
|
||||
label: "All",
|
||||
};
|
||||
|
||||
const RecordFilter: React.FC<Props> = ({
|
||||
user,
|
||||
entities,
|
||||
users,
|
||||
filterState,
|
||||
assignments = true,
|
||||
children
|
||||
user,
|
||||
entities,
|
||||
users,
|
||||
filterState,
|
||||
assignments = true,
|
||||
children
|
||||
}) => {
|
||||
const { filter, setFilter } = filterState;
|
||||
const { filter, setFilter } = filterState;
|
||||
|
||||
const [entity, setEntity] = useState<string>()
|
||||
const [entity, setEntity] = useState<string>()
|
||||
|
||||
const [, setStatsUserId] = useRecordStore((state) => [
|
||||
state.selectedUser,
|
||||
state.setSelectedUser
|
||||
]);
|
||||
const [, setStatsUserId] = useRecordStore((state) => [
|
||||
state.selectedUser,
|
||||
state.setSelectedUser
|
||||
]);
|
||||
|
||||
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [users, entity])
|
||||
const allowedViewEntities = useAllowedEntities(user, entities, 'view_student_record')
|
||||
|
||||
useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id])
|
||||
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [users, entity])
|
||||
|
||||
const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
|
||||
setFilter((prev) => (prev === value ? undefined : value));
|
||||
};
|
||||
useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id])
|
||||
|
||||
return (
|
||||
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
|
||||
<div className="xl:w-3/4 flex gap-2">
|
||||
{checkAccess(user, ["developer", "admin", "mastercorporate"]) && !children && (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
|
||||
setFilter((prev) => (prev === value ? undefined : value));
|
||||
};
|
||||
|
||||
<Select
|
||||
options={entities.map((e) => ({value: e.id, label: e.label}))}
|
||||
onChange={(value) => setEntity(value?.value || undefined)}
|
||||
isClearable
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<label className="font-normal text-base text-mti-gray-dim">User</label>
|
||||
return (
|
||||
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
|
||||
<div className="xl:w-3/4 flex gap-2">
|
||||
{checkAccess(user, ["developer", "admin", "mastercorporate"]) && !children && (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
|
||||
<Select
|
||||
options={entityUsers.map((x) => ({
|
||||
value: x.id,
|
||||
label: `${x.name} - ${x.email}`,
|
||||
}))}
|
||||
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
||||
onChange={(value) => setStatsUserId(value?.value!)}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{(user.type === "corporate" || user.type === "teacher") && !children && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="font-normal text-base text-mti-gray-dim">User</label>
|
||||
<Select
|
||||
options={allowedViewEntities.map((e) => ({ value: e.id, label: e.label }))}
|
||||
onChange={(value) => setEntity(value?.value || undefined)}
|
||||
isClearable
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<label className="font-normal text-base text-mti-gray-dim">User</label>
|
||||
|
||||
<Select
|
||||
options={users
|
||||
.map((x) => ({
|
||||
value: x.id,
|
||||
label: `${x.name} - ${x.email}`,
|
||||
}))}
|
||||
defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}}
|
||||
onChange={(value) => setStatsUserId(value?.value!)}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex gap-4 w-full justify-center xl:justify-end">
|
||||
{assignments && (
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "assignments" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("assignments")}>
|
||||
Assignments
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "months" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("months")}>
|
||||
Last month
|
||||
</button>
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "weeks" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("weeks")}>
|
||||
Last week
|
||||
</button>
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "days" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("days")}>
|
||||
Last day
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<Select
|
||||
options={entityUsers.map((x) => ({
|
||||
value: x.id,
|
||||
label: `${x.name} - ${x.email}`,
|
||||
}))}
|
||||
defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }}
|
||||
onChange={(value) => setStatsUserId(value?.value!)}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{(user.type === "corporate" || user.type === "teacher") && !children && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="font-normal text-base text-mti-gray-dim">User</label>
|
||||
|
||||
<Select
|
||||
options={users
|
||||
.map((x) => ({
|
||||
value: x.id,
|
||||
label: `${x.name} - ${x.email}`,
|
||||
}))}
|
||||
defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }}
|
||||
onChange={(value) => setStatsUserId(value?.value!)}
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
option: (styles, state) => ({
|
||||
...styles,
|
||||
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
||||
color: state.isFocused ? "black" : styles.color,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex gap-4 w-full justify-center xl:justify-end">
|
||||
{assignments && (
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "assignments" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("assignments")}>
|
||||
Assignments
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "months" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("months")}>
|
||||
Last month
|
||||
</button>
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "weeks" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("weeks")}>
|
||||
Last week
|
||||
</button>
|
||||
<button
|
||||
className={clsx(
|
||||
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||
"transition duration-300 ease-in-out",
|
||||
filter === "days" && "!bg-mti-purple-light !text-white",
|
||||
)}
|
||||
onClick={() => toggleFilter("days")}>
|
||||
Last day
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RecordFilter;
|
||||
|
||||
@@ -82,7 +82,7 @@ interface StatsGridItemProps {
|
||||
selectedTrainingExams?: string[];
|
||||
maxTrainingExams?: number;
|
||||
setSelectedTrainingExams?: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
renderPdfIcon: (session: string, color: string, textColor: string) => React.ReactNode;
|
||||
renderPdfIcon?: (session: string, color: string, textColor: string) => React.ReactNode;
|
||||
}
|
||||
|
||||
const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
@@ -236,7 +236,7 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
||||
{renderLevelScore()}
|
||||
</span>
|
||||
)}
|
||||
{shouldRenderPDFIcon() && renderPdfIcon(session, textColor, textColor)}
|
||||
{shouldRenderPDFIcon() && renderPdfIcon && renderPdfIcon(session, textColor, textColor)}
|
||||
</div>
|
||||
{examNumber === undefined ? (
|
||||
<>
|
||||
|
||||
@@ -65,28 +65,28 @@ export default function Navbar({ user, path, navDisabled = false, focusMode = fa
|
||||
{
|
||||
module: "reading",
|
||||
icon: () => <BsBook className="h-4 w-4 text-white" />,
|
||||
achieved: user.levels?.reading || 0 >= user.desiredLevels?.reading || 9,
|
||||
achieved: user?.levels?.reading || 0 >= user?.desiredLevels?.reading || 9,
|
||||
},
|
||||
|
||||
{
|
||||
module: "listening",
|
||||
icon: () => <BsHeadphones className="h-4 w-4 text-white" />,
|
||||
achieved: user.levels?.listening || 0 >= user.desiredLevels?.listening || 9,
|
||||
achieved: user?.levels?.listening || 0 >= user?.desiredLevels?.listening || 9,
|
||||
},
|
||||
{
|
||||
module: "writing",
|
||||
icon: () => <BsPen className="h-4 w-4 text-white" />,
|
||||
achieved: user.levels?.writing || 0 >= user.desiredLevels?.writing || 9,
|
||||
achieved: user?.levels?.writing || 0 >= user?.desiredLevels?.writing || 9,
|
||||
},
|
||||
{
|
||||
module: "speaking",
|
||||
icon: () => <BsMegaphone className="h-4 w-4 text-white" />,
|
||||
achieved: user.levels?.speaking || 0 >= user.desiredLevels?.speaking || 9,
|
||||
achieved: user?.levels?.speaking || 0 >= user?.desiredLevels?.speaking || 9,
|
||||
},
|
||||
{
|
||||
module: "level",
|
||||
icon: () => <BsClipboard className="h-4 w-4 text-white" />,
|
||||
achieved: user.levels?.level || 0 >= user.desiredLevels?.level || 9,
|
||||
achieved: user?.levels?.level || 0 >= user?.desiredLevels?.level || 9,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import {PaymentIntention} from "@/interfaces/paymob";
|
||||
import {DurationUnit} from "@/interfaces/paypal";
|
||||
import {User} from "@/interfaces/user";
|
||||
import { Entity } from "@/interfaces/entity";
|
||||
import { PaymentIntention } from "@/interfaces/paymob";
|
||||
import { DurationUnit } from "@/interfaces/paypal";
|
||||
import { User } from "@/interfaces/user";
|
||||
import axios from "axios";
|
||||
import {useRouter} from "next/router";
|
||||
import {useState} from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import Button from "./Low/Button";
|
||||
import Input from "./Low/Input";
|
||||
import Modal from "./Modal";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
entity?: Entity
|
||||
currency: string;
|
||||
price: number;
|
||||
setIsPaymentLoading: (v: boolean) => void;
|
||||
@@ -18,7 +20,7 @@ interface Props {
|
||||
onSuccess: (duration: number, duration_unit: DurationUnit) => void;
|
||||
}
|
||||
|
||||
export default function PaymobPayment({user, price, setIsPaymentLoading, currency, duration, duration_unit, onSuccess}: Props) {
|
||||
export default function PaymobPayment({ user, entity, price, setIsPaymentLoading, currency, duration, duration_unit, onSuccess }: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -56,10 +58,11 @@ export default function PaymobPayment({user, price, setIsPaymentLoading, currenc
|
||||
userID: user.id,
|
||||
duration,
|
||||
duration_unit,
|
||||
entity: entity?.id
|
||||
},
|
||||
};
|
||||
|
||||
const response = await axios.post<{iframeURL: string}>(`/api/paymob`, paymentIntention);
|
||||
const response = await axios.post<{ iframeURL: string }>(`/api/paymob`, paymentIntention);
|
||||
|
||||
router.push(response.data.iframeURL);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user