Updated the expiry date to be based on the expiry date
This commit is contained in:
@@ -4,6 +4,11 @@ export interface Entity {
|
||||
id: string;
|
||||
label: string;
|
||||
licenses: number;
|
||||
expiryDate?: Date | null
|
||||
payment?: {
|
||||
currency: string
|
||||
price: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
|
||||
@@ -32,6 +32,7 @@ export type DurationUnit = "weeks" | "days" | "months" | "years";
|
||||
export interface Payment {
|
||||
id: string;
|
||||
corporate: string;
|
||||
entity?: string
|
||||
agent?: string;
|
||||
agentCommission: number;
|
||||
agentValue: number;
|
||||
|
||||
@@ -6,9 +6,11 @@ import { deleteEntity, getEntity, getEntityWithRoles } from "@/utils/entities.be
|
||||
import client from "@/lib/mongodb";
|
||||
import { Entity } from "@/interfaces/entity";
|
||||
import { doesEntityAllow } from "@/utils/permissions";
|
||||
import { getUser } from "@/utils/users.be";
|
||||
import { getEntityUsers, getUser } from "@/utils/users.be";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import { filterBy, mapBy } from "@/utils";
|
||||
import { User } from "@/interfaces/user";
|
||||
|
||||
const db = client.db(process.env.MONGODB_DB);
|
||||
|
||||
@@ -66,5 +68,22 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
|
||||
return res.status(200).json({ ok: entity.acknowledged });
|
||||
}
|
||||
|
||||
if (req.body.expiryDate !== undefined) {
|
||||
const entity = await getEntity(id)
|
||||
const result = await db.collection<Entity>("entities").updateOne({ id }, { $set: { expiryDate: req.body.expiryDate } });
|
||||
|
||||
const users = await getEntityUsers(id, 0, {
|
||||
subscriptionExpirationDate: entity?.expiryDate,
|
||||
$and: [
|
||||
{ type: { $ne: "admin" } },
|
||||
{ type: { $ne: "developer" } },
|
||||
]
|
||||
})
|
||||
|
||||
await db.collection<User>("users").updateMany({ id: { $in: mapBy(users, 'id') } }, { $set: { subscriptionExpirationDate: req.body.expiryDate } })
|
||||
|
||||
return res.status(200).json({ ok: result.acknowledged });
|
||||
}
|
||||
|
||||
return res.status(200).json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import CardList from "@/components/High/CardList";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import Select from "@/components/Low/Select";
|
||||
import Checkbox from "@/components/Low/Checkbox";
|
||||
import Tooltip from "@/components/Low/Tooltip";
|
||||
import { useEntityPermission } from "@/hooks/useEntityPermissions";
|
||||
import { useListSearch } from "@/hooks/useListSearch";
|
||||
@@ -27,7 +28,10 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Divider } from "primereact/divider";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
|
||||
import {
|
||||
BsCheck,
|
||||
BsChevronLeft,
|
||||
BsClockFill,
|
||||
BsEnvelopeFill,
|
||||
@@ -43,6 +47,15 @@ import {
|
||||
} from "react-icons/bs";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const expirationDateColor = (date: Date) => {
|
||||
const momentDate = moment(date);
|
||||
const today = moment(new Date());
|
||||
|
||||
if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light";
|
||||
if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light";
|
||||
if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light";
|
||||
};
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => {
|
||||
const user = req.session.user as User;
|
||||
|
||||
@@ -88,6 +101,7 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
const [expiryDate, setExpiryDate] = useState(entity?.expiryDate)
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -99,6 +113,7 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
const canRemoveMembers = useEntityPermission(user, entity, "remove_from_entity")
|
||||
|
||||
const canAssignRole = useEntityPermission(user, entity, "assign_to_role")
|
||||
const canPay = useEntityPermission(user, entity, 'pay_entity')
|
||||
|
||||
const toggleUser = (u: User) => setSelectedUsers((prev) => (prev.includes(u.id) ? prev.filter((p) => p !== u.id) : [...prev, u.id]));
|
||||
|
||||
@@ -166,6 +181,23 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
const updateExpiryDate = () => {
|
||||
if (!isAdmin(user)) return;
|
||||
|
||||
setIsLoading(true);
|
||||
axios
|
||||
.patch(`/api/entities/${entity.id}`, { expiryDate })
|
||||
.then(() => {
|
||||
toast.success("The entity has been updated successfully!");
|
||||
router.replace(router.asPath);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.error("Something went wrong!");
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
const editLicenses = () => {
|
||||
if (!isAdmin(user)) return;
|
||||
|
||||
@@ -289,7 +321,7 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
<Layout user={user}>
|
||||
<section className="flex flex-col gap-0">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/entities"
|
||||
@@ -298,6 +330,20 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
</Link>
|
||||
<h2 className="font-bold text-2xl">{entity.label} {isAdmin(user) && `- ${entity.licenses || 0} licenses`}</h2>
|
||||
</div>
|
||||
|
||||
{!isAdmin(user) && canPay && (
|
||||
<Link
|
||||
href="/payment"
|
||||
className={clsx(
|
||||
"p-2 w-full max-w-[200px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
!entity.expiryDate ? "!bg-mti-green-ultralight !border-mti-green-light" : expirationDateColor(entity.expiryDate),
|
||||
"bg-white border-mti-gray-platinum",
|
||||
)}>
|
||||
{!entity.expiryDate && "Unlimited"}
|
||||
{entity.expiryDate && moment(entity.expiryDate).format("DD/MM/YYYY")}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
@@ -332,6 +378,61 @@ export default function Home({ user, entity, users, linkedUsers }: Props) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isAdmin(user) && (
|
||||
<>
|
||||
<Divider />
|
||||
|
||||
<div className="w-full flex justify-between items-center">
|
||||
<div className="flex items-center gap-4 w-full">
|
||||
{!!expiryDate && (
|
||||
<ReactDatePicker
|
||||
className={clsx(
|
||||
"p-2 w-full max-w-[200px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"hover:border-mti-purple tooltip",
|
||||
!expiryDate ? "!bg-mti-green-ultralight !border-mti-green-light" : expirationDateColor(expiryDate),
|
||||
"transition duration-300 ease-in-out",
|
||||
)}
|
||||
filterDate={(date) => moment(date).isAfter(new Date())}
|
||||
dateFormat="dd/MM/yyyy"
|
||||
selected={expiryDate ? moment(expiryDate).toDate() : null}
|
||||
onChange={(date) => setExpiryDate(date)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!expiryDate && (
|
||||
<div
|
||||
className={clsx(
|
||||
"p-2 w-full max-w-[200px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
!expiryDate ? "!bg-mti-green-ultralight !border-mti-green-light" : expirationDateColor(expiryDate),
|
||||
"bg-white border-mti-gray-platinum",
|
||||
)}
|
||||
>
|
||||
Unlimited
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Checkbox
|
||||
isChecked={!!expiryDate}
|
||||
onChange={(checked: boolean) => setExpiryDate(checked ? entity.expiryDate || new Date() : null)}
|
||||
>
|
||||
Enable expiry date
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
onClick={updateExpiryDate}
|
||||
disabled={expiryDate === entity.expiryDate}
|
||||
className="flex w-fit text-nowrap items-center gap-1 px-2 py-2 border rounded-full border-mti-green bg-mti-green-light text-white hover:bg-mti-green-dark disabled:hover:bg-mti-green-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
|
||||
<BsCheck />
|
||||
<span className="text-xs">Apply Change</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<span className="font-semibold text-xl">Members ({users.length})</span>
|
||||
|
||||
@@ -98,7 +98,9 @@ const ENTITY_MANAGEMENT: PermissionLayout[] = [
|
||||
{ label: "Delete Entity Role", key: "delete_entity_role" },
|
||||
{ label: "Download Statistics Report", key: "download_statistics_report" },
|
||||
{ label: "Edit Grading System", key: "edit_grading_system" },
|
||||
{ label: "View Student Performance", key: "view_student_performance" }
|
||||
{ label: "View Student Performance", key: "view_student_performance" },
|
||||
{ label: "Pay for Entity", key: "pay_entity" },
|
||||
{ label: "View Payment Record", key: "view_payment_record" }
|
||||
]
|
||||
|
||||
const ASSIGNMENT_MANAGEMENT: PermissionLayout[] = [
|
||||
|
||||
@@ -30,9 +30,12 @@ import { toFixedNumber } from "@/utils/number";
|
||||
import { CSVLink } from "react-csv";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { useListSearch } from "@/hooks/useListSearch";
|
||||
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
|
||||
import { checkAccess, findAllowedEntities, getTypesOfUser } from "@/utils/permissions";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { redirect } from "@/utils";
|
||||
import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import { Entity, EntityWithRoles } from "@/interfaces/entity";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res)
|
||||
@@ -42,8 +45,13 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
return redirect("/")
|
||||
}
|
||||
|
||||
const entityIDs = mapBy(user.entities, 'id')
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs)
|
||||
|
||||
const allowedEntities = findAllowedEntities(user, entities, "view_payment_record")
|
||||
|
||||
return {
|
||||
props: { user },
|
||||
props: serialize({ user, entities: allowedEntities }),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
@@ -273,7 +281,13 @@ interface PaypalPaymentWithUserData extends PaypalPayment {
|
||||
}
|
||||
|
||||
const paypalFilterRows = [["email"], ["name"], ["orderId"], ["value"]];
|
||||
export default function PaymentRecord() {
|
||||
|
||||
interface Props {
|
||||
user: User
|
||||
entities: EntityWithRoles[]
|
||||
}
|
||||
|
||||
export default function PaymentRecord({ user, entities }: Props) {
|
||||
const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>();
|
||||
const [selectedAgentUser, setSelectedAgentUser] = useState<User>();
|
||||
const [isCreatingPayment, setIsCreatingPayment] = useState(false);
|
||||
@@ -281,9 +295,9 @@ export default function PaymentRecord() {
|
||||
const [displayPayments, setDisplayPayments] = useState<Payment[]>([]);
|
||||
|
||||
const [corporate, setCorporate] = useState<User>();
|
||||
const [entity, setEntity] = useState<Entity>();
|
||||
const [agent, setAgent] = useState<User>();
|
||||
|
||||
const { user } = useUser({ redirectTo: "/login" });
|
||||
const { users, reload: reloadUsers } = useUsers();
|
||||
const { payments: originalPayments, reload: reloadPayment } = usePayments();
|
||||
const { payments: paypalPayments, reload: reloadPaypalPayment } = usePaypalPayments();
|
||||
@@ -341,17 +355,17 @@ export default function PaymentRecord() {
|
||||
|
||||
useEffect(() => {
|
||||
setFilters((prev) => [
|
||||
...prev.filter((x) => x.id !== "corporate-filter"),
|
||||
...(!corporate
|
||||
...prev.filter((x) => x.id !== "entity-filter"),
|
||||
...(!entity
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: "corporate-filter",
|
||||
filter: (p: Payment) => p.corporate === corporate.id,
|
||||
id: "entity-filter",
|
||||
filter: (p: Payment) => p.entity === entity.id,
|
||||
},
|
||||
]),
|
||||
]);
|
||||
}, [corporate]);
|
||||
}, [entity]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilters((prev) => [
|
||||
@@ -675,7 +689,7 @@ export default function PaymentRecord() {
|
||||
<Checkbox
|
||||
isChecked={value}
|
||||
onChange={(e) => {
|
||||
if (user?.type === agent || user?.type === "corporate" || value) return null;
|
||||
if (user?.type === "agent" || user?.type === "corporate" || value) return null;
|
||||
if (!info.row.original.commissionTransfer || !info.row.original.corporateTransfer)
|
||||
return alert("All files need to be uploaded to consider it paid!");
|
||||
if (!confirm(`Are you sure you want to consider this payment paid?`)) return null;
|
||||
|
||||
@@ -7,7 +7,7 @@ import PaymentDue from "./(status)/PaymentDue";
|
||||
import { useRouter } from "next/router";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { getEntities } from "@/utils/entities.be";
|
||||
import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import { User } from "@/interfaces/user";
|
||||
@@ -17,7 +17,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
if (!user) return redirect("/login")
|
||||
|
||||
const entityIDs = mapBy(user.entities, 'id')
|
||||
const entities = await getEntities(isAdmin(user) ? undefined : entityIDs)
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDs)
|
||||
|
||||
return {
|
||||
props: serialize({ user, entities }),
|
||||
|
||||
@@ -63,7 +63,9 @@ export type RolePermission =
|
||||
"upload_classroom" |
|
||||
"download_user_list" |
|
||||
"view_student_record" |
|
||||
"download_student_record"
|
||||
"download_student_record" |
|
||||
"pay_entity" |
|
||||
"view_payment_record"
|
||||
|
||||
export const DEFAULT_PERMISSIONS: RolePermission[] = [
|
||||
"view_students",
|
||||
@@ -140,5 +142,7 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [
|
||||
"upload_classroom",
|
||||
"download_user_list",
|
||||
"view_student_record",
|
||||
"download_student_record"
|
||||
"download_student_record",
|
||||
"pay_entity",
|
||||
"view_payment_record"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user