/* eslint-disable @next/next/no-img-element */ import Head from "next/head"; import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import useUser from "@/hooks/useUser"; import {toast, ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; import {shouldRedirectHome} from "@/utils/navigation.disabled"; import usePayments from "@/hooks/usePayments"; import {Payment} from "@/interfaces/paypal"; import {CellContext, createColumnHelper, flexRender, getCoreRowModel, HeaderGroup, useReactTable} from "@tanstack/react-table"; import {CURRENCIES} from "@/resources/paypal"; import {BsTrash} from "react-icons/bs"; import axios from "axios"; import {useEffect, useState, useMemo} from "react"; import {AgentUser, CorporateUser, User} from "@/interfaces/user"; import UserCard from "@/components/UserCard"; import Modal from "@/components/Modal"; import clsx from "clsx"; import useUsers from "@/hooks/useUsers"; import Checkbox from "@/components/Low/Checkbox"; import Button from "@/components/Low/Button"; import Select from "react-select"; import Input from "@/components/Low/Input"; import ReactDatePicker from "react-datepicker"; import moment from "moment"; import PaymentAssetManager from "@/components/PaymentAssetManager"; import {toFixedNumber} from "@/utils/number"; import {CSVLink} from "react-csv"; export const getServerSideProps = withIronSessionSsr(({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { res.setHeader("location", "/login"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } if (shouldRedirectHome(user) || !["admin", "developer"].includes(user.type)) { res.setHeader("location", "/"); res.statusCode = 302; res.end(); return { props: { user: null, }, }; } return { props: {user: req.session.user}, }; }, sessionOptions); const columnHelper = createColumnHelper(); const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () => void; reload: () => void; showComission: boolean}) => { const [corporate, setCorporate] = useState(); const [date, setDate] = useState(new Date()); const {users} = useUsers(); const price = corporate?.corporateInformation?.payment?.value || 0; const commission = corporate?.corporateInformation?.payment?.commission || 0; const currency = corporate?.corporateInformation?.payment?.currency || "EUR"; const referralAgent = useMemo(() => { if (corporate?.corporateInformation?.referralAgent) { return users.find((u) => u.id === corporate.corporateInformation.referralAgent); } return undefined; }, [corporate?.corporateInformation?.referralAgent, users]); const submit = () => { axios .post(`/api/payments`, { corporate: corporate?.id, agent: referralAgent?.id, agentCommission: commission, agentValue: toFixedNumber((commission! / 100) * price!, 2), currency, value: price, isPaid: false, date: date.toISOString(), }) .then(() => { toast.success("New payment has been created successfully!"); reload(); onClose(); }) .catch(() => { toast.error("Something went wrong, please try again later!"); }); }; return (

New Payment

{}} type="number" value={price} defaultValue={0} className="col-span-3" disabled /> {}} type="number" defaultValue={0} value={commission} disabled />
c.currency === currency)?.label}`} onChange={() => null} type="text" defaultValue={0} disabled />
)}
setDate(date ?? new Date())} />
null} type="text" defaultValue={"No country manager"} disabled />
); }; const IS_PAID_OPTIONS = [ { value: null, label: "All", }, { value: false, label: "Unpaid", }, { value: true, label: "Paid", }, ]; const IS_FILE_SUBMITTED_OPTIONS = [ { value: null, label: "All", }, { value: false, label: "Submitted", }, { value: true, label: "Not Submitted", }, ]; const CSV_WHITELISTED_KEYS = ["corporateId", "corporate", "date", "amount", "agent", "agentCommission", "agentValue", "isPaid"]; interface SimpleCSVColumn { key: string; label: string; index: number; } export default function PaymentRecord() { const [selectedCorporateUser, setSelectedCorporateUser] = useState(); const [selectedAgentUser, setSelectedAgentUser] = useState(); const [isCreatingPayment, setIsCreatingPayment] = useState(false); const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]); const [displayPayments, setDisplayPayments] = useState([]); const [corporate, setCorporate] = useState(); const [agent, setAgent] = useState(); const {user} = useUser({redirectTo: "/login"}); const {users, reload: reloadUsers} = useUsers(); const {payments: originalPayments, reload: reloadPayment} = usePayments(); const [startDate, setStartDate] = useState(moment("01/01/2023").toDate()); const [endDate, setEndDate] = useState(moment().endOf("day").toDate()); const [paid, setPaid] = useState(IS_PAID_OPTIONS[0].value); const [commissionTransfer, setCommissionTransfer] = useState(IS_FILE_SUBMITTED_OPTIONS[0].value); const [corporateTransfer, setCorporateTransfer] = useState(IS_FILE_SUBMITTED_OPTIONS[0].value); const reload = () => { reloadUsers(); reloadPayment(); }; const payments = useMemo(() => { return originalPayments.filter((p: Payment) => { const date = moment(p.date); return date.isAfter(startDate) && date.isBefore(endDate); }); }, [originalPayments, startDate, endDate]); useEffect(() => { setDisplayPayments( filters .map((f) => f.filter) .reduce((d, f) => d.filter(f), payments) .sort((a, b) => moment(b.date).diff(moment(a.date))), ); }, [payments, filters]); useEffect(() => { if (user && user.type === "agent") { setAgent(user); } }, [user]); useEffect(() => { setFilters((prev) => [ ...prev.filter((x) => x.id !== "agent-filter"), ...(!agent ? [] : [{id: "agent-filter", filter: (p: Payment) => p.agent === agent.id}]), ]); }, [agent]); useEffect(() => { setFilters((prev) => [ ...prev.filter((x) => x.id !== "corporate-filter"), ...(!corporate ? [] : [{id: "corporate-filter", filter: (p: Payment) => p.corporate === corporate.id}]), ]); }, [corporate]); useEffect(() => { setFilters((prev) => [ ...prev.filter((x) => x.id !== "paid"), ...(typeof paid !== "boolean" ? [] : [{id: "paid", filter: (p: Payment) => p.isPaid === paid}]), ]); }, [paid]); useEffect(() => { setFilters((prev) => [ ...prev.filter((x) => x.id !== "commissionTransfer"), ...(typeof commissionTransfer !== "boolean" ? [] : [{id: "commissionTransfer", filter: (p: Payment) => !p.commissionTransfer === commissionTransfer}]), ]); }, [commissionTransfer]); useEffect(() => { setFilters((prev) => [ ...prev.filter((x) => x.id !== "corporateTransfer"), ...(typeof corporateTransfer !== "boolean" ? [] : [{id: "corporateTransfer", filter: (p: Payment) => !p.corporateTransfer === corporateTransfer}]), ]); }, [corporateTransfer]); const updatePayment = (payment: Payment, key: string, value: any) => { axios .patch(`api/payments/${payment.id}`, {...payment, [key]: value}) .then(() => toast.success("Updated the payment")) .finally(reload); }; const deletePayment = (id: string) => { if (!confirm(`Are you sure you want to delete this payment?`)) return; axios .delete(`/api/payments/${id}`) .then(() => toast.success(`Deleted the "${id}" payment`)) .catch((reason) => { if (reason.response.status === 404) { toast.error("Exam not found!"); return; } if (reason.response.status === 403) { toast.error("You do not have permission to delete this exam!"); return; } toast.error("Something went wrong, please try again later."); }) .finally(reload); }; const getFileAssetsColumns = () => { if (user) { const containerClassName = "flex gap-2 text-mti-purple-light hover:text-mti-purple-dark ease-in-out duration-300 cursor-pointer"; switch (user.type) { case "corporate": return [ columnHelper.accessor("corporateTransfer", { header: "Corporate transfer", id: "corporateTransfer", cell: (info) => (
), }), ]; case "agent": return [ columnHelper.accessor("commissionTransfer", { header: "Commission transfer", id: "commissionTransfer", cell: (info) => (
), }), ]; case "admin": return [ columnHelper.accessor("corporateTransfer", { header: "Corporate transfer", id: "corporateTransfer", cell: (info) => (
), }), columnHelper.accessor("commissionTransfer", { header: "Commission transfer", id: "commissionTransfer", cell: (info) => (
), }), ]; case "developer": return [ columnHelper.accessor("corporateTransfer", { header: "Corporate transfer", id: "corporateTransfer", cell: (info) => (
), }), columnHelper.accessor("commissionTransfer", { header: "Commission transfer", id: "commissionTransfer", cell: (info) => (
), }), ]; default: return []; } } return []; }; const columHelperValue = (key: string, info: any) => { switch (key) { case "agentCommission": { const value = info.getValue(); return {value: `${value}%`}; } case "agent": { const user = users.find((x) => x.id === info.row.original.agent) as AgentUser; return { value: user?.name, user, }; } case "agentValue": case "amount": { const value = info.getValue(); const numberValue = toFixedNumber(value, 2); return {value: numberValue}; } case "date": { const value = info.getValue(); return {value: moment(value).format("DD/MM/YYYY")}; } case "corporate": { const specificValue = info.row.original.corporate; const user = users.find((x) => x.id === specificValue) as CorporateUser; return { user, value: user?.corporateInformation.companyInformation.name || user?.name, }; } case "currency": { return { value: info.row.original.currency, }; } case "isPaid": case "corporateId": default: { const value = info.getValue(); return {value}; } } }; const defaultColumns = [ columnHelper.accessor("corporate", { header: "Corporate ID", id: "corporateId", cell: (info) => { const {value} = columHelperValue(info.column.id, info); return value; }, }), columnHelper.accessor("corporate", { header: "Corporate", id: "corporate", cell: (info) => { const {user, value} = columHelperValue(info.column.id, info); return (
setSelectedCorporateUser(user)}> {value}
); }, }), columnHelper.accessor("date", { header: "Date", id: "date", cell: (info) => { const {value} = columHelperValue(info.column.id, info); return {value}; }, }), columnHelper.accessor("value", { header: "Amount", id: "amount", cell: (info) => { const {value} = columHelperValue(info.column.id, info); const currency = CURRENCIES.find((x) => x.currency === info.row.original.currency)?.label; const finalValue = `${value} ${currency}`; return {finalValue}; }, }), columnHelper.accessor("agent", { header: "Country Manager", id: "agent", cell: (info) => { const {user, value} = columHelperValue(info.column.id, info); return (
setSelectedAgentUser(user)}> {value}
); }, }), columnHelper.accessor("agentCommission", { header: "Commission", id: "agentCommission", cell: (info) => { const {value} = columHelperValue(info.column.id, info); return <>{value}; }, }), columnHelper.accessor("agentValue", { header: "Commission Value", id: "agentValue", cell: (info) => { const {value} = columHelperValue(info.column.id, info); const currency = CURRENCIES.find((x) => x.currency === info.row.original.currency)?.label; const finalValue = `${value} ${currency}`; return {finalValue}; }, }), columnHelper.accessor("isPaid", { header: "Paid", id: "isPaid", cell: (info) => { const {value} = columHelperValue(info.column.id, info); return ( { if (user?.type === agent || 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; return updatePayment(info.row.original, "isPaid", e); }}> ); }, }), ...getFileAssetsColumns(), { header: "", id: "actions", cell: ({row}: {row: {original: Payment}}) => { return (
{user?.type !== "agent" && (
deletePayment(row.original.id)}>
)}
); }, }, ]; const table = useReactTable({ data: displayPayments, columns: defaultColumns, getCoreRowModel: getCoreRowModel(), }); const getUserModal = () => { if (user) { if (selectedCorporateUser) { return ( setSelectedCorporateUser(undefined)}> <> {selectedCorporateUser && (
{ setSelectedCorporateUser(undefined); if (shouldReload) reload(); }} user={selectedCorporateUser} disabled />
)}
); } if (selectedAgentUser) { return ( setSelectedAgentUser(undefined)}> <> {selectedAgentUser && (
{ setSelectedAgentUser(undefined); if (shouldReload) reload(); }} user={selectedAgentUser} />
)}
); } } return null; }; const getCSVData = () => { const columns = table.getHeaderGroups().reduce((accm: SimpleCSVColumn[], group: HeaderGroup) => { const whitelistedColumns = group.headers.filter((header) => CSV_WHITELISTED_KEYS.includes(header.id)); const data = whitelistedColumns.map((data) => ({ key: data.column.columnDef.id, label: data.column.columnDef.header, })) as SimpleCSVColumn[]; return [...accm, ...data]; }, []); const {rows} = table.getRowModel(); const finalColumns = [ ...columns, { key: "currency", label: "Currency", }, ]; return { columns: finalColumns, rows: rows.map((row) => { return finalColumns.reduce((accm, {key}) => { const {value} = columHelperValue(key, { row, getValue: () => row.getValue(key), }); return { ...accm, [key]: value, }; }, {}); }), }; }; const {rows: csvRows, columns: csvColumns} = getCSVData(); return ( <> Payment Record | EnCoach {user && ( {getUserModal()} setIsCreatingPayment(false)}> setIsCreatingPayment(false)} reload={reload} showComission={user.type === "developer" || user.type === "admin"} />

Payment Record

{(user.type === "developer" || user.type === "admin") && (
)}
u.type === "agent") as AgentUser[]).map((user) => ({ value: user.id, meta: user, label: `${user.name} - ${user.email}`, }))} value={agent ? {value: agent?.id, label: `${agent.name} - ${agent.email}`} : undefined} onChange={(value) => setAgent(value !== null ? (value as any).meta : undefined)} styles={{ control: (styles) => ({ ...styles, paddingLeft: "4px", border: "none", outline: "none", ":focus": { outline: "none", }, }), option: (styles, state) => ({ ...styles, backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", color: state.isFocused ? "black" : styles.color, }), }} />
e.value === commissionTransfer)} onChange={(value) => { if (value) return setCommissionTransfer(value.value); setCommissionTransfer(null); }} styles={{ control: (styles) => ({ ...styles, paddingLeft: "4px", border: "none", outline: "none", ":focus": { outline: "none", }, }), option: (styles, state) => ({ ...styles, backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", color: state.isFocused ? "black" : styles.color, }), }} />