ENCOA-272

This commit is contained in:
Tiago Ribeiro
2024-12-11 11:58:52 +00:00
parent 6a6c4661c4
commit d074ec390c
46 changed files with 1940 additions and 6423 deletions

View File

@@ -1,15 +1,15 @@
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import {CorporateInformation, CorporateUser, EMPLOYMENT_STATUS, User, Type, Stat, Gender} from "@/interfaces/user";
import {groupBySession, averageScore} from "@/utils/stats";
import {RadioGroup} from "@headlessui/react";
import { CorporateInformation, CorporateUser, EMPLOYMENT_STATUS, User, Type, Stat, Gender } from "@/interfaces/user";
import { groupBySession, averageScore } from "@/utils/stats";
import { RadioGroup } from "@headlessui/react";
import axios from "axios";
import clsx from "clsx";
import moment from "moment";
import {Divider} from "primereact/divider";
import {useEffect, useState} from "react";
import { Divider } from "primereact/divider";
import { useEffect, useState } from "react";
import ReactDatePicker from "react-datepicker";
import {BsFileEarmarkText, BsPencil, BsPerson, BsPersonAdd, BsStar} from "react-icons/bs";
import {toast} from "react-toastify";
import { BsFileEarmarkText, BsPencil, BsPerson, BsPersonAdd, BsStar } from "react-icons/bs";
import { toast } from "react-toastify";
import Button from "./Low/Button";
import Checkbox from "./Low/Checkbox";
import CountrySelect from "./Low/CountrySelect";
@@ -17,12 +17,12 @@ import Input from "./Low/Input";
import ProfileSummary from "./ProfileSummary";
import Select from "react-select";
import useUsers from "@/hooks/useUsers";
import {USER_TYPE_LABELS} from "@/resources/user";
import {CURRENCIES} from "@/resources/paypal";
import { USER_TYPE_LABELS } from "@/resources/user";
import { CURRENCIES } from "@/resources/paypal";
import useCodes from "@/hooks/useCodes";
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {PERMISSIONS} from "@/constants/userPermissions";
import {PermissionType} from "@/interfaces/permissions";
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { PERMISSIONS } from "@/constants/userPermissions";
import { PermissionType } from "@/interfaces/permissions";
import usePermissions from "@/hooks/usePermissions";
const expirationDateColor = (date: Date) => {
@@ -68,7 +68,7 @@ const USER_TYPE_OPTIONS = Object.keys(USER_TYPE_LABELS).map((type) => ({
label: USER_TYPE_LABELS[type as keyof typeof USER_TYPE_LABELS],
}));
const CURRENCIES_OPTIONS = CURRENCIES.map(({label, currency}) => ({
const CURRENCIES_OPTIONS = CURRENCIES.map(({ label, currency }) => ({
value: currency,
label,
}));
@@ -100,9 +100,7 @@ const UserCard = ({
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.referralAgent : undefined,
);
const [companyName, setCompanyName] = useState(
user.type === "corporate" || user.type === "mastercorporate"
? user.corporateInformation?.companyInformation.name
: user.type === "agent"
user.type === "agent"
? user.agentInformation?.companyName
: undefined,
);
@@ -110,25 +108,19 @@ const UserCard = ({
const [commercialRegistration, setCommercialRegistration] = useState(
user.type === "agent" ? user.agentInformation?.commercialRegistration : undefined,
);
const [userAmount, setUserAmount] = useState(
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.companyInformation.userAmount : undefined,
);
const [paymentValue, setPaymentValue] = useState(
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.payment?.value : undefined,
);
const [paymentCurrency, setPaymentCurrency] = useState(
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.payment?.currency : "EUR",
);
const [monthlyDuration, setMonthlyDuration] = useState(
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.monthlyDuration : undefined,
);
const [commissionValue, setCommission] = useState(
user.type === "corporate" || user.type === "mastercorporate" ? user.corporateInformation?.payment?.commission : undefined,
);
const {data: stats} = useFilterRecordsByUser<Stat[]>(user.id);
const {users} = useUsers();
const {codes} = useCodes(user.id);
const {permissions} = usePermissions(loggedInUser.id);
const { data: stats } = useFilterRecordsByUser<Stat[]>(user.id);
const { users } = useUsers();
const { codes } = useCodes(user.id);
const { permissions } = usePermissions(loggedInUser.id);
useEffect(() => {
if (users && users.length > 0) {
@@ -153,7 +145,7 @@ const UserCard = ({
if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return;
axios
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
.post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
...user,
subscriptionExpirationDate: expiryDate,
studentID,
@@ -167,26 +159,21 @@ const UserCard = ({
agentInformation:
type === "agent"
? {
companyName,
commercialRegistration,
arabName,
}
companyName,
commercialRegistration,
arabName,
}
: undefined,
corporateInformation:
type === "corporate" || type === "mastercorporate"
? {
referralAgent,
monthlyDuration,
companyInformation: {
name: companyName,
userAmount,
},
payment: {
value: paymentValue,
currency: paymentCurrency,
...(referralAgent === "" ? {} : {commission: commissionValue}),
},
}
referralAgent,
payment: {
value: paymentValue,
currency: paymentCurrency,
...(referralAgent === "" ? {} : { commission: commissionValue }),
},
}
: undefined,
})
.then(() => {
@@ -194,7 +181,7 @@ const UserCard = ({
onClose(true);
})
.catch(() => {
toast.error("Something went wrong!", {toastId: "update-error"});
toast.error("Something went wrong!", { toastId: "update-error" });
});
};
@@ -216,31 +203,16 @@ const UserCard = ({
},
];
const corporateProfileItems =
user.type === "corporate" || user.type === "mastercorporate"
? [
{
icon: <BsPerson className="w-6 h-6 md:w-8 md:h-8 text-mti-red-light" />,
value: codes.length,
label: "Users Used",
},
{
icon: <BsPersonAdd className="w-6 h-6 md:w-8 md:h-8 text-mti-red-light" />,
value: user.corporateInformation?.companyInformation?.userAmount,
label: "Number of Users",
},
]
: [];
const updateUserPermission = PERMISSIONS.updateUser[user.type] as {
list: Type[];
perm: PermissionType;
};
return (
<>
<ProfileSummary
user={user}
items={user.type === "corporate" || user.type === "mastercorporate" ? corporateProfileItems : generalProfileItems}
items={user.type === "corporate" || user.type === "mastercorporate" ? [] : generalProfileItems}
/>
{user.type === "agent" && (
@@ -283,48 +255,6 @@ const UserCard = ({
{(user.type === "corporate" || user.type === "mastercorporate") && (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 w-full">
<Input
label="Corporate Name"
type="text"
name="companyName"
onChange={setCompanyName}
placeholder="Enter corporate name"
defaultValue={companyName}
disabled={
disabled ||
checkAccess(
loggedInUser,
getTypesOfUser(
user.type === "mastercorporate" ? ["developer", "admin"] : ["developer", "admin", "mastercorporate"],
),
)
}
/>
<Input
label="Number of Users"
type="number"
name="userAmount"
max={maxUserAmount}
onChange={(e) => setUserAmount(e ? parseInt(e) : undefined)}
placeholder="Enter number of users"
defaultValue={userAmount}
disabled={
disabled ||
checkAccess(
loggedInUser,
getTypesOfUser(["developer", "admin", ...((user.type === "corporate" ? ["mastercorporate"] : []) as Type[])]),
)
}
/>
<Input
label="Monthly Duration"
type="number"
name="monthlyDuration"
onChange={(e) => setMonthlyDuration(e ? parseInt(e) : undefined)}
placeholder="Enter monthly duration"
defaultValue={monthlyDuration}
disabled={disabled || checkAccess(loggedInUser, getTypesOfUser(["developer", "admin"]))}
/>
<div className="flex flex-col gap-3 w-full lg:col-span-3">
<label className="font-normal text-base text-mti-gray-dim">Pricing</label>
<div className="w-full grid grid-cols-6 gap-2">
@@ -346,7 +276,7 @@ const UserCard = ({
onChange={(value) => setPaymentCurrency(value?.value)}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -375,10 +305,10 @@ const UserCard = ({
className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-white rounded-full border border-mti-gray-platinum focus:outline-none",
(checkAccess(loggedInUser, getTypesOfUser(["developer", "admin"])) || disabledFields.countryManager) &&
"!bg-mti-gray-platinum/40 !text-mti-gray-dim cursor-not-allowed",
"!bg-mti-gray-platinum/40 !text-mti-gray-dim cursor-not-allowed",
)}
options={[
{value: "", label: "No referral"},
{ value: "", label: "No referral" },
...users
.filter((u) => u.type === "agent")
.map((x) => ({
@@ -393,7 +323,7 @@ const UserCard = ({
menuPortalTarget={document?.body}
onChange={(value) => setReferralAgent(value?.value)}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -505,9 +435,9 @@ const UserCard = ({
value={user.demographicInformation?.employment}
className="grid grid-cols-2 items-center gap-4 place-items-center"
disabled={disabled}>
{EMPLOYMENT_STATUS.map(({status, label}) => (
{EMPLOYMENT_STATUS.map(({ status, label }) => (
<RadioGroup.Option value={status} key={status}>
{({checked}) => (
{({ checked }) => (
<span
className={clsx(
"px-6 py-4 w-40 md:w-48 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
@@ -545,7 +475,7 @@ const UserCard = ({
className="flex flex-row gap-4 justify-between"
disabled={disabled}>
<RadioGroup.Option value="male">
{({checked}) => (
{({ checked }) => (
<span
className={clsx(
"px-6 py-4 w-28 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
@@ -559,7 +489,7 @@ const UserCard = ({
)}
</RadioGroup.Option>
<RadioGroup.Option value="female">
{({checked}) => (
{({ checked }) => (
<span
className={clsx(
"px-6 py-4 w-28 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
@@ -573,7 +503,7 @@ const UserCard = ({
)}
</RadioGroup.Option>
<RadioGroup.Option value="other">
{({checked}) => (
{({ checked }) => (
<span
className={clsx(
"px-6 py-4 w-28 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
@@ -643,92 +573,92 @@ const UserCard = ({
permissions,
user.type === "teacher" ? "editTeacher" : user.type === "student" ? "editStudent" : undefined,
) && (
<>
<Divider className="w-full !m-0" />
<div className="flex flex-col md:flex-row gap-8 w-full">
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Status</label>
<Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={USER_STATUS_OPTIONS.filter((x) => {
if (checkAccess(loggedInUser, ["admin", "developer"])) return true;
return x.value !== "paymentDue";
})}
menuPortalTarget={document?.body}
value={USER_STATUS_OPTIONS.find((o) => o.value === status)}
onChange={(value) => setStatus(value?.value as typeof user.status)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
<>
<Divider className="w-full !m-0" />
<div className="flex flex-col md:flex-row gap-8 w-full">
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Status</label>
<Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={USER_STATUS_OPTIONS.filter((x) => {
if (checkAccess(loggedInUser, ["admin", "developer"])) return true;
return x.value !== "paymentDue";
})}
menuPortalTarget={document?.body}
value={USER_STATUS_OPTIONS.find((o) => o.value === status)}
onChange={(value) => setStatus(value?.value as typeof user.status)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
},
}),
menuPortal: (base) => ({...base, zIndex: 9999}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
</div>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Type</label>
<Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={USER_TYPE_OPTIONS.filter((x) => {
if (x.value === "student")
return checkAccess(
loggedInUser,
["developer", "admin", "corporate", "mastercorporate"],
permissions,
"editStudent",
);
":focus": {
outline: "none",
},
}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
</div>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Type</label>
<Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={USER_TYPE_OPTIONS.filter((x) => {
if (x.value === "student")
return checkAccess(
loggedInUser,
["developer", "admin", "corporate", "mastercorporate"],
permissions,
"editStudent",
);
if (x.value === "teacher")
return checkAccess(
loggedInUser,
["developer", "admin", "corporate", "mastercorporate"],
permissions,
"editTeacher",
);
if (x.value === "teacher")
return checkAccess(
loggedInUser,
["developer", "admin", "corporate", "mastercorporate"],
permissions,
"editTeacher",
);
if (x.value === "corporate")
return checkAccess(loggedInUser, ["developer", "admin", "mastercorporate"], permissions, "editCorporate");
if (x.value === "corporate")
return checkAccess(loggedInUser, ["developer", "admin", "mastercorporate"], permissions, "editCorporate");
return checkAccess(loggedInUser, ["developer", "admin"]);
})}
menuPortalTarget={document?.body}
value={USER_TYPE_OPTIONS.find((o) => o.value === type)}
onChange={(value) => setType(value?.value as typeof user.type)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
return checkAccess(loggedInUser, ["developer", "admin"]);
})}
menuPortalTarget={document?.body}
value={USER_TYPE_OPTIONS.find((o) => o.value === type)}
onChange={(value) => setType(value?.value as typeof user.type)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
},
}),
menuPortal: (base) => ({...base, zIndex: 9999}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
":focus": {
outline: "none",
},
}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
isDisabled={disabled}
/>
</div>
</div>
</div>
</>
)}
</>
)}
</section>
<div className="flex gap-4 justify-between mt-4 w-full">