Added search text to Paypal payments

Updated CSV Downlaod
Reordered the UI of the page
This commit is contained in:
Joao Ramos
2024-02-26 18:46:21 +00:00
parent d7f1a4f6b2
commit 9b5ff70037

View File

@@ -37,7 +37,7 @@ import PaymentAssetManager from "@/components/PaymentAssetManager";
import { toFixedNumber } from "@/utils/number"; import { toFixedNumber } from "@/utils/number";
import { CSVLink } from "react-csv"; import { CSVLink } from "react-csv";
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
import { useListSearch } from "@/hooks/useListSearch";
export const getServerSideProps = withIronSessionSsr(({ req, res }) => { export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
const user = req.session.user; const user = req.session.user;
@@ -69,7 +69,7 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
}, sessionOptions); }, sessionOptions);
const columnHelper = createColumnHelper<Payment>(); const columnHelper = createColumnHelper<Payment>();
const paypalColumnHelper = createColumnHelper<PaypalPayment>(); const paypalColumnHelper = createColumnHelper<PaypalPaymentWithUserData>();
const PaymentCreator = ({ const PaymentCreator = ({
onClose, onClose,
@@ -348,7 +348,7 @@ const IS_FILE_SUBMITTED_OPTIONS = [
}, },
]; ];
const CSV_WHITELISTED_KEYS = [ const CSV_PAYMENTS_WHITELISTED_KEYS = [
"corporateId", "corporateId",
"corporate", "corporate",
"date", "date",
@@ -359,12 +359,28 @@ const CSV_WHITELISTED_KEYS = [
"isPaid", "isPaid",
]; ];
const CSV_PAYPAL_WHITELISTED_KEYS = [
"orderId",
"status",
"name",
"email",
"value",
"createdAt",
"subscriptionExpirationDate",
];
interface SimpleCSVColumn { interface SimpleCSVColumn {
key: string; key: string;
label: string; label: string;
index: number; index: number;
} }
interface PaypalPaymentWithUserData extends PaypalPayment {
name: string;
email: string;
}
const paypalFilterRows = [["email"], ["name"]];
export default function PaymentRecord() { export default function PaymentRecord() {
const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>(); const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>();
const [selectedAgentUser, setSelectedAgentUser] = useState<User>(); const [selectedAgentUser, setSelectedAgentUser] = useState<User>();
@@ -407,6 +423,8 @@ export default function PaymentRecord() {
}); });
}, [originalPayments, startDate, endDate]); }, [originalPayments, startDate, endDate]);
const [selectedIndex, setSelectedIndex] = useState(0);
useEffect(() => { useEffect(() => {
setDisplayPayments( setDisplayPayments(
filters filters
@@ -839,6 +857,15 @@ export default function PaymentRecord() {
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
const updatedPaypalPayments = useMemo(
() =>
paypalPayments.map((p) => {
const user = users.find((x) => x.id === p.userId) as User;
return { ...p, name: user?.name, email: user?.email };
}),
[paypalPayments, users]
);
const paypalColumns = [ const paypalColumns = [
paypalColumnHelper.accessor("orderId", { paypalColumnHelper.accessor("orderId", {
header: "Order ID", header: "Order ID",
@@ -856,24 +883,20 @@ export default function PaymentRecord() {
return <span>{value}</span>; return <span>{value}</span>;
}, },
}), }),
paypalColumnHelper.accessor("userId", { paypalColumnHelper.accessor("name", {
header: "User Name", header: "User Name",
id: "name", id: "name",
cell: (info) => { cell: (info) => {
const { value } = columHelperValue("userId", info); const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
const user = users.find((x) => x.id === value) as User;
return <span>{user?.name}</span>;
}, },
}), }),
paypalColumnHelper.accessor("userId", { paypalColumnHelper.accessor("email", {
header: "Email", header: "Email",
id: "email", id: "email",
cell: (info) => { cell: (info) => {
const { value } = columHelperValue("userId", info); const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
const user = users.find((x) => x.id === value) as User;
return <span>{user?.email}</span>;
}, },
}), }),
paypalColumnHelper.accessor("value", { paypalColumnHelper.accessor("value", {
@@ -906,8 +929,13 @@ export default function PaymentRecord() {
}), }),
]; ];
const { rows: filteredRows, renderSearch } = useListSearch(
paypalFilterRows,
updatedPaypalPayments
);
const paypalTable = useReactTable({ const paypalTable = useReactTable({
data: paypalPayments, data: filteredRows,
columns: paypalColumns, columns: paypalColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -967,11 +995,18 @@ export default function PaymentRecord() {
}; };
const getCSVData = () => { const getCSVData = () => {
const columns = table const tables = [table, paypalTable];
const whitelists = [
CSV_PAYMENTS_WHITELISTED_KEYS,
CSV_PAYPAL_WHITELISTED_KEYS,
];
const currentTable = tables[selectedIndex];
const whitelist = whitelists[selectedIndex];
const columns = currentTable
.getHeaderGroups() .getHeaderGroups()
.reduce((accm: SimpleCSVColumn[], group: HeaderGroup<Payment>) => { .reduce((accm: SimpleCSVColumn[], group: HeaderGroup<Payment>) => {
const whitelistedColumns = group.headers.filter((header) => const whitelistedColumns = group.headers.filter((header) =>
CSV_WHITELISTED_KEYS.includes(header.id) whitelist.includes(header.id)
); );
const data = whitelistedColumns.map((data) => ({ const data = whitelistedColumns.map((data) => ({
@@ -982,7 +1017,7 @@ export default function PaymentRecord() {
return [...accm, ...data]; return [...accm, ...data];
}, []); }, []);
const { rows } = table.getRowModel(); const { rows } = currentTable.getRowModel();
const finalColumns = [ const finalColumns = [
...columns, ...columns,
@@ -1045,6 +1080,7 @@ export default function PaymentRecord() {
</tbody> </tbody>
</table> </table>
); );
return ( return (
<> <>
<Head> <Head>
@@ -1099,283 +1135,7 @@ export default function PaymentRecord() {
)} )}
</div> </div>
</div> </div>
<div <Tab.Group selectedIndex={selectedIndex} onChange={setSelectedIndex}>
className={clsx(
"grid grid-cols-1 md:grid-cols-2 gap-8 w-full",
user.type !== "corporate" && "lg:grid-cols-3"
)}
>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Corporate account *
</label>
<Select
isClearable={user.type !== "corporate"}
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",
user.type === "corporate" &&
"!bg-mti-gray-platinum/40 !text-mti-gray-dim !cursor-not-allowed"
)}
options={(
users.filter((u) => u.type === "corporate") as CorporateUser[]
).map((user) => ({
value: user.id,
meta: user,
label: `${
user.corporateInformation?.companyInformation?.name ||
user.name
} - ${user.email}`,
}))}
defaultValue={
user.type === "corporate"
? {
value: user.id,
meta: user,
label: `${
user.corporateInformation?.companyInformation?.name ||
user.name
} - ${user.email}`,
}
: undefined
}
isDisabled={user.type === "corporate"}
onChange={(value) =>
setCorporate((value as any)?.meta ?? undefined)
}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
{user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Country manager *
</label>
<Select
isClearable
isDisabled={user.type === "agent"}
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent"
? "bg-mti-gray-platinum/40"
: "bg-white"
)}
options={(
users.filter((u) => 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)
}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
)}
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Paid
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent" ? "bg-mti-gray-platinum/40" : "bg-white"
)}
options={IS_PAID_OPTIONS}
value={IS_PAID_OPTIONS.find((e) => e.value === paid)}
onChange={(value) => {
if (value) return setPaid(value.value);
setPaid(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Date
</label>
<ReactDatePicker
dateFormat="dd/MM/yyyy"
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
selected={startDate}
startDate={startDate}
endDate={endDate}
selectsRange
showMonthDropdown
filterDate={(date: Date) =>
moment(date).isSameOrBefore(moment(new Date()))
}
onChange={([initialDate, finalDate]: [Date, Date]) => {
setStartDate(initialDate ?? moment("01/01/2023").toDate());
if (finalDate) {
// basicly selecting a final day works as if I'm selecting the first
// minute of that day. this way it covers the whole day
setEndDate(moment(finalDate).endOf("day").toDate());
return;
}
setEndDate(null);
}}
/>
</div>
{user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Commission transfer
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none"
)}
options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find(
(e) => e.value === commissionTransfer
)}
onChange={(value) => {
if (value) return setCommissionTransfer(value.value);
setCommissionTransfer(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
)}
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Corporate transfer
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none"
)}
options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find(
(e) => e.value === corporateTransfer
)}
onChange={(value) => {
if (value) return setCorporateTransfer(value.value);
setCorporateTransfer(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
</div>
<Tab.Group>
<Tab.List className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1"> <Tab.List className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">
<Tab <Tab
className={({ selected }) => className={({ selected }) =>
@@ -1406,12 +1166,297 @@ export default function PaymentRecord() {
Paypal Paypal
</Tab> </Tab>
</Tab.List> </Tab.List>
<Tab.Panels className="mt-2"> <Tab.Panels>
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide"> <Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide gap-8 flex flex-col gap-8">
<div
className={clsx(
"grid grid-cols-1 md:grid-cols-2 gap-8 w-full",
user.type !== "corporate" && "lg:grid-cols-3"
)}
>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Corporate account *
</label>
<Select
isClearable={user.type !== "corporate"}
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",
user.type === "corporate" &&
"!bg-mti-gray-platinum/40 !text-mti-gray-dim !cursor-not-allowed"
)}
options={(
users.filter(
(u) => u.type === "corporate"
) as CorporateUser[]
).map((user) => ({
value: user.id,
meta: user,
label: `${
user.corporateInformation?.companyInformation?.name ||
user.name
} - ${user.email}`,
}))}
defaultValue={
user.type === "corporate"
? {
value: user.id,
meta: user,
label: `${
user.corporateInformation?.companyInformation
?.name || user.name
} - ${user.email}`,
}
: undefined
}
isDisabled={user.type === "corporate"}
onChange={(value) =>
setCorporate((value as any)?.meta ?? undefined)
}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
{user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Country manager *
</label>
<Select
isClearable
isDisabled={user.type === "agent"}
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent"
? "bg-mti-gray-platinum/40"
: "bg-white"
)}
options={(
users.filter((u) => 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
)
}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
)}
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Paid
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent"
? "bg-mti-gray-platinum/40"
: "bg-white"
)}
options={IS_PAID_OPTIONS}
value={IS_PAID_OPTIONS.find((e) => e.value === paid)}
onChange={(value) => {
if (value) return setPaid(value.value);
setPaid(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Date
</label>
<ReactDatePicker
dateFormat="dd/MM/yyyy"
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
selected={startDate}
startDate={startDate}
endDate={endDate}
selectsRange
showMonthDropdown
filterDate={(date: Date) =>
moment(date).isSameOrBefore(moment(new Date()))
}
onChange={([initialDate, finalDate]: [Date, Date]) => {
setStartDate(
initialDate ?? moment("01/01/2023").toDate()
);
if (finalDate) {
// basicly selecting a final day works as if I'm selecting the first
// minute of that day. this way it covers the whole day
setEndDate(moment(finalDate).endOf("day").toDate());
return;
}
setEndDate(null);
}}
/>
</div>
{user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Commission transfer
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none"
)}
options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find(
(e) => e.value === commissionTransfer
)}
onChange={(value) => {
if (value) return setCommissionTransfer(value.value);
setCommissionTransfer(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
)}
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">
Corporate transfer
</label>
<Select
isClearable
className={clsx(
"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 rounded-full border border-mti-gray-platinum focus:outline-none"
)}
options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find(
(e) => e.value === corporateTransfer
)}
onChange={(value) => {
if (value) return setCorporateTransfer(value.value);
setCorporateTransfer(null);
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
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,
}),
}}
/>
</div>
</div>
{renderTable(table as Table<Payment>)} {renderTable(table as Table<Payment>)}
</Tab.Panel> </Tab.Panel>
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide"> <Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide flex flex-col gap-8">
{renderTable(paypalTable as Table<PaypalPayment>)} {renderSearch()}
{renderTable(paypalTable as Table<PaypalPaymentWithUserData>)}
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>