UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

420 lines (419 loc) 15.2 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { useLocaleContext } from "@arcblock/ux/lib/Locale/context"; import { Box, Typography, Grid, Stack, Link, Button } from "@mui/material"; import { useRequest } from "ahooks"; import { useNavigate } from "react-router-dom"; import React, { useEffect, useRef, useState } from "react"; import { styled } from "@mui/system"; import { joinURL } from "ufo"; import DateRangePicker from "../../components/date-range-picker.js"; import { formatToDate, getPrefix, formatCreditAmount, formatBNStr } from "../../libs/util.js"; import { usePaymentContext } from "../../contexts/payment.js"; import api from "../../libs/api.js"; import Table from "../../components/table.js"; import { createLink, handleNavigation } from "../../libs/navigation.js"; const fetchData = (params = {}) => { const search = new URLSearchParams(); Object.keys(params).forEach((key) => { if (params[key]) { search.set(key, String(params[key])); } }); return api.get(`/api/credit-transactions?${search.toString()}`).then((res) => res.data); }; const getGrantDetailLink = (grantId, inDashboard) => { let path = `/customer/credit-grant/${grantId}`; if (inDashboard) { path = `/admin/customers/${grantId}`; } return { link: createLink(path), connect: false }; }; const getInvoiceDetailLink = (invoiceId, inDashboard) => { let path = `/customer/invoice/${invoiceId}`; if (inDashboard) { path = `/admin/billing/${invoiceId}`; } return { link: createLink(path), connect: false }; }; const getSubscriptionDetailLink = (subscriptionId, inDashboard) => { let path = `/customer/subscription/${subscriptionId}`; if (inDashboard) { path = `/admin/billing/${subscriptionId}`; } return { link: createLink(path), connect: false }; }; const getCreditTransactionDetailLink = (transactionId) => { const path = `/customer/credit-transaction/${transactionId}`; return { link: createLink(path), connect: false }; }; const getMeterEventDetailLink = (meterEventId) => { const path = `/admin/billing/${meterEventId}`; return { link: createLink(path), connect: false }; }; const getSubscriptionId = (item) => item.metadata?.subscription_id || item.subscription_id || item.invoice?.subscription_id; const getInvoiceId = (item) => item.metadata?.invoice_id || item.invoice?.id; const getMeterEventId = (item) => item.source || item.metadata?.meter_event_id; const getCreditActivityFlags = (item) => { const isGrant = item.activity_type === "grant"; const isScheduled = isGrant && item.metadata?.delivery_mode === "schedule"; const isDepleted = isGrant && item.status === "depleted"; const isExpired = isGrant && (item.status === "expired" || item.status === "voided"); const isInactive = isDepleted || isExpired; return { isGrant, isScheduled, isDepleted, isExpired, isInactive }; }; const getTransactionDetailLink = (item, inDashboard) => { if (item.activity_type === "grant") { const invoiceId = getInvoiceId(item); if (invoiceId) { return getInvoiceDetailLink(invoiceId, inDashboard); } return getGrantDetailLink(item.id, inDashboard); } if (!inDashboard) { return getCreditTransactionDetailLink(item.id); } const meterEventId = getMeterEventId(item); if (!meterEventId) { return null; } return getMeterEventDetailLink(meterEventId); }; const getTransactionDescription = (item, t) => { const { isGrant, isScheduled, isInactive } = getCreditActivityFlags(item); const isPaid = isGrant && item.category === "paid" && (!isScheduled || item.metadata?.schedule_seq === 1); if (!isGrant) { const secondLine = item.metadata?.is_repayment ? t("common.creditActivity.repayment") : item.description || ""; return { isGrant, isInactive, activityType: t("common.creditActivity.consumption"), secondLine }; } if (isPaid) { let secondLine = item.description || ""; if (item.invoice?.total && item.invoice?.paymentCurrency) { const invoiceCurrency = item.invoice.paymentCurrency; const paidAmount = formatCreditAmount( formatBNStr(item.invoice.total, invoiceCurrency.decimal || 0), invoiceCurrency.symbol || "" ); secondLine = t("common.creditActivity.paidAmount", { amount: paidAmount }); } return { isGrant, isInactive, activityType: t("common.creditActivity.paidGrant"), secondLine }; } if (isScheduled) { return { isGrant, isInactive, activityType: t("common.creditActivity.resetGrant"), secondLine: item.description || "" }; } return { isGrant, isInactive, activityType: t("common.creditActivity.promotionalGrant"), secondLine: item.description || "" }; }; const TransactionsTable = React.memo((props) => { const { pageSize, customer_id, subscription_id, credit_grant_id, onTableDataChange, showAdminColumns = false, showTimeFilter = false, includeGrants = false, source, mode = "portal" } = props; const listKey = "credit-transactions-table"; const { t, locale } = useLocaleContext(); const { session } = usePaymentContext(); const isAdmin = ["owner", "admin"].includes(session?.user?.role || ""); const isDashboard = isAdmin && mode === "dashboard"; const navigate = useNavigate(); const effectiveCustomerId = customer_id || session?.user?.did; const [search, setSearch] = useState({ pageSize: pageSize || 10, page: 1 }); const [filters, setFilters] = useState({ start: void 0, end: void 0 }); const handleDateRangeChange = (newValue) => { setFilters(newValue); setSearch((prev) => ({ ...prev, page: 1, start: newValue.start || void 0, end: newValue.end || void 0 })); }; const { loading, data = { list: [], count: 0 } } = useRequest( () => fetchData({ ...search, customer_id: effectiveCustomerId, subscription_id, credit_grant_id, source, include_grants: includeGrants }), { refreshDeps: [search, effectiveCustomerId, subscription_id, credit_grant_id, source, includeGrants] } ); useEffect(() => { if (showTimeFilter && !search.start && !search.end) { setSearch((prev) => ({ ...prev, page: 1, start: filters.start || void 0, end: filters.end || void 0 })); } }, [showTimeFilter, search.start, search.end, filters.start, filters.end]); const prevData = useRef(data); useEffect(() => { if (onTableDataChange) { onTableDataChange(data, prevData.current); prevData.current = data; } }, [data]); const handleTransactionClick = (e, item) => { const detail = getTransactionDetailLink(item, isDashboard); if (!detail) { return; } handleNavigation(e, detail.link, navigate, { target: detail.link.external ? "_blank" : "_self" }); }; const openSubscription = (e, subscriptionId) => { e.preventDefault(); const link = getSubscriptionDetailLink(subscriptionId, isDashboard); handleNavigation(e, link.link, navigate); }; const openInvoice = (e, invoiceId) => { e.preventDefault(); const link = getInvoiceDetailLink(invoiceId, isDashboard); handleNavigation(e, link.link, navigate); }; const renderActionButton = (label, onClick) => /* @__PURE__ */ jsx(Button, { variant: "text", size: "small", color: "primary", sx: { whiteSpace: "nowrap" }, onClick, children: label }); const columns = [ { label: t("common.date"), name: "created_at", options: { setCellProps: () => ({ style: { width: "25%" } }), customBodyRenderLite: (_, index) => { const item = data?.list[index]; return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { fontSize: "0.875rem" }, children: formatToDate(item.created_at, locale, "YYYY-MM-DD HH:mm") }) }); } } }, { label: t("common.description"), name: "description", options: { setCellProps: () => ({ style: { width: "25%" } }), customBodyRenderLite: (_, index) => { const item = data?.list[index]; const { activityType, secondLine, isInactive, isGrant } = getTransactionDescription(item, t); return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), sx: { cursor: "pointer" }, children: /* @__PURE__ */ jsxs(Stack, { direction: "column", spacing: 0.25, children: [ /* @__PURE__ */ jsx( Typography, { variant: "body2", sx: { color: isInactive ? "text.secondary" : isGrant ? "success.main" : "error.main" }, children: activityType } ), secondLine && /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: secondLine }) ] }) }); } } }, { label: t("common.amount"), name: "credit_amount", align: "right", options: { setCellProps: () => ({ style: { width: "20%" } }), customHeadLabelRender: () => /* @__PURE__ */ jsx(Box, { sx: { pr: 5 }, children: t("common.amount") }), customBodyRenderLite: (_, index) => { const item = data?.list[index]; const { isGrant, isDepleted, isExpired, isInactive } = getCreditActivityFlags(item); const amount = isGrant ? item.amount : item.credit_amount; const currency = item.paymentCurrency || item.currency; const unit = !isGrant && item.meter?.unit ? item.meter.unit : currency?.symbol; const displayAmount = formatCreditAmount(formatBNStr(amount, currency?.decimal || 0), unit); if (!includeGrants) { return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), sx: { pr: 5 }, children: /* @__PURE__ */ jsx(Typography, { children: displayAmount }) }); } return /* @__PURE__ */ jsx(Box, { onClick: (e) => handleTransactionClick(e, item), sx: { pr: 5 }, children: /* @__PURE__ */ jsxs(Stack, { direction: "column", spacing: 0.25, alignItems: "flex-end", children: [ /* @__PURE__ */ jsxs( Typography, { sx: { fontWeight: 500, color: isInactive ? "text.secondary" : isGrant ? "success.main" : "error.main", whiteSpace: "nowrap" }, children: [ isGrant ? "+" : "-", " ", displayAmount ] } ), isDepleted ? /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: t("common.consumed") }) : isExpired ? /* @__PURE__ */ jsx(Typography, { variant: "caption", sx: { color: "text.secondary" }, children: t("common.expired") }) : null ] }) }); } } }, ...showAdminColumns && isAdmin ? [ { label: t("common.meterEvent"), name: "meter_event", options: { customBodyRenderLite: (_, index) => { const transaction = data?.list[index]; if (!transaction.meter) { return /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "-" }); } return /* @__PURE__ */ jsx(Link, { href: joinURL(getPrefix(), `/admin/billing/${transaction.meter.id}`), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { color: "text.link" }, children: transaction.meter.event_name }) }); } } } ] : [], { label: t("common.actions"), name: "actions", options: { setCellProps: () => ({ style: { width: "25%" } }), customBodyRenderLite: (_, index) => { const item = data?.list[index]; const { isGrant, isScheduled } = getCreditActivityFlags(item); const isPaid = isGrant && item.category === "paid" && !isScheduled; const subscriptionId = getSubscriptionId(item); const invoiceId = isGrant ? getInvoiceId(item) : null; const shouldShowSubscription = Boolean(subscriptionId) && (!isGrant || isScheduled || isPaid); return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", gap: 1, alignItems: "center", flexWrap: "nowrap" }, children: [ shouldShowSubscription && subscriptionId ? renderActionButton(t("common.viewSubscription"), (e) => openSubscription(e, subscriptionId)) : null, isPaid && invoiceId ? renderActionButton(t("common.viewInvoice"), (e) => openInvoice(e, invoiceId)) : null ] }); } } } ].filter(Boolean); const onTableChange = ({ page, rowsPerPage }) => { if (search.pageSize !== rowsPerPage) { setSearch((x) => ({ ...x, pageSize: rowsPerPage, page: 1 })); } else if (search.page !== page + 1) { setSearch((x) => ({ ...x, page: page + 1 })); } }; return /* @__PURE__ */ jsxs(TableRoot, { children: [ showTimeFilter && /* @__PURE__ */ jsx(Box, { sx: { my: 2 }, children: /* @__PURE__ */ jsx(Box, { sx: { mt: 2 }, children: /* @__PURE__ */ jsx( Grid, { container: true, spacing: 2, sx: { alignItems: "center" }, children: /* @__PURE__ */ jsx( Grid, { size: { xs: 12, sm: 6, md: 4 }, children: /* @__PURE__ */ jsx(DateRangePicker, { value: filters, onChange: handleDateRangeChange, size: "small", fullWidth: true }) } ) } ) }) }), /* @__PURE__ */ jsx( Table, { hasRowLink: true, durable: `__${listKey}__`, durableKeys: ["page", "rowsPerPage"], data: data.list, columns, options: { count: data.count, page: search.page - 1, rowsPerPage: search.pageSize }, loading, onChange: onTableChange, toolbar: false, sx: { mt: 2 }, showMobile: false, mobileTDFlexDirection: "row", emptyNodeText: t("admin.creditTransactions.noTransactions") } ) ] }); }); const TableRoot = styled(Box)` @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) { .MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root { > div { width: fit-content; flex: inherit; font-size: 14px; } } .invoice-summary { padding-right: 20px; } } `; export default function CreditTransactionsList(rawProps) { const props = Object.assign( { customer_id: "", subscription_id: "", credit_grant_id: "", source: "", pageSize: 10, onTableDataChange: () => { }, showAdminColumns: false, showTimeFilter: false, includeGrants: false, mode: "portal" }, rawProps ); return /* @__PURE__ */ jsx(TransactionsTable, { ...props }); }