/* eslint-disable prettier/prettier */ import { Anchor, Breadcrumbs, Button, Group, LoadingOverlay, Modal, Paper, Text, TextInput, Tooltip, Title, ActionIcon, Badge, Grid, InputWrapper, Input, Textarea, Space, Alert, Center, MultiSelect, Checkbox, CheckboxGroup, NumberInput, RadioGroup, Radio, } from "@mantine/core"; import { showNotification } from "@mantine/notifications"; import { CaretSortIcon, ResetIcon } from "@radix-ui/react-icons"; import Link from "next/link"; import React, { useEffect, useState, useRef } from "react"; import DataTable from "react-data-table-component"; import { useDispatch, useSelector } from "react-redux"; import { getConfig, isConfigLoaded } from "reducers/configReducer"; import { render } from "sass"; import ltcService from "services/ltcService"; import styled from "styled-components"; import { Row } from "styles/styledComponents"; import { Config } from "types/config"; import { LtcUser } from "types/user"; import translations from "../translations.json"; import { Edit, BrandProducthunt, News, VideoPlus, Video, Affiliate, Replace, CirclePlus, Filter, AlertCircle, Calendar, ListDetails, } from "tabler-icons-react"; import { useRouter } from "next/router"; import { faArrowRotateLeft, faAward, faCertificate, faCoffee, faEye, faUserEdit, faVideoCamera, faCompress, faPen, faRss, faUpload, faTrash, faEyeSlash, faRetweet, faTimes, faDownload, faCopy, faList, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import moment from "moment"; import { DatePicker } from "@mantine/dates"; import { useDebouncedValue, useForm } from "@mantine/hooks"; import SubscriptionStatus from "../components/subscriptionStatus"; import { IconSearch } from "@tabler/icons"; const dataTableStyle = { width: "98%", headCells: { style: { fontSize: "16px", fontWeight: "bold", }, }, cells: { style: { fontSize: "14px", }, }, }; const checkBoxStyle = { padding: "0%", textAlign: "left", fontStyle: "normal", wordWrap: "break-word", // maxWidth:"20%", }; const FailedPayments = () => { const dispatch = useDispatch(); const configLoaded = useSelector(isConfigLoaded); const config: Config = useSelector(getConfig); const router = useRouter(); const { id, inputStatus, inputFilter, pagenumber, pagesize, inputSortField, inputSortDirection, } = router.query; const [openResendDialog, setOpenResendDialog] = useState(false); const [loader, setLoader] = useState(false); const [packageLoading, setPackageLoading] = useState(false); const [data, setData] = useState([]); const [totalRows, setTotalRows] = useState(0); const [title, setTitle] = useState(""); const [paginationTotalRows, setPaginationTotalRows] = useState(0); const [searchTerm, setSearchTerm] = useState(""); const [openPreviewDialog, setOpenPreviewDialog] = useState(false); const [previewData, setPreviewData] = useState(); const [spinner, setSpinner] = React.useState(false); const [pageNumber, setPageNumber] = useState(pagenumber ? pagenumber : 1); const [pageSize, setPageSize] = useState(pagesize ? pagesize : 100); const [sortField, setSortField] = useState( inputSortField ? inputSortField : "startTime", ); const [sortDirection, setSortDirection] = useState( inputSortDirection ? inputSortDirection : "DESC", ); const [toBeResendRecepId, setToBeResendRecepId] = useState(); const [templateIdentifiers, setTemplateIdentifiers] = useState([]); const [selectedTempId, setSelectedTemp] = useState([]); const [filter, setFilter] = useState(inputFilter ? inputFilter : ""); const [debouncedFilter] = useDebouncedValue(filter, 200); //NEW FIELDS const [selectedStatuses, setSelectedStatuses] = useState([]); const [auto, setAuto] = useState([]); const [todate, setToDate] = useState(); const [fromdate, setFromDate] = useState(); const [fromAmount, setFromAmount] = useState(); const [userId, setUserId] = useState(); const [email, setEmail] = useState(); const [toamount, setToAmount] = useState(); const [status, setStatus] = useState([]); const [packages, setPackages] = useState([]); const [products, setProducts] = useState([]); const [selectedProducts, setSelectedProducts] = useState([]); const [selectedPackages, setSelectedPackages] = useState([]); const [copyLabel, setCopyLable] = useState("Copy"); const [emiData, setEmiData] = useState([]); const [openEmiDialog, setOpenEmiDialog] = useState(false); useEffect(() => { if (inputStatus) { if (typeof inputStatus == "object") { setSelectedStatuses(inputStatus); } else { setSelectedStatuses([inputStatus]); } } if (configLoaded && !inputStatus && !inputFilter) { loadData(); } }, [configLoaded]); useEffect(() => { if (configLoaded) { filterData(); } }, [ pageNumber, pageSize, sortField, sortDirection, debouncedFilter, selectedStatuses, ]); useEffect(() => { if (products.length == 0) loadProducts(); }, []); function loadEmiData(row) { ltcService .get( `${config.paymentApiUrl}/payment-service/api/v1/subscription/${row.subscriptionId}/installment`, ) .then((response) => { setEmiData(response.data); }) .catch((e) => { setLoader(false); }); } async function loadProducts() { const url = `${config.productApiUrl}/product-service/api/v1/product`; const response = await ltcService .get(url) .then((response) => { const data = []; response.data.forEach((el) => { data.push({ label: el["name"], value: el["id"], }); }); setProducts(data); }) .catch((e) => { setLoader(false); }); } async function loadData() { setLoader(true); let url = `${config.paymentApiUrl}/payment-service/admin/api/v1/installment/failedpayments`; url = url + `?pageNumber=${pageNumber}&pageSize=${pageSize}&sortField=${sortField}&sortDirection=${sortDirection}`; const response = await ltcService .get(url) .then((response) => { handleDeepLink(); setData(response.data.data); setTotalRows(response.data.pagination.total); setPageNumber(response.data.pagination.pageNumber); // setPaginationTotalRows(response.data.pagination.total); setLoader(false); }) .catch((e) => { setLoader(false); }); } const handleSort = async (column, sortDirection) => { /// reach out to some API and get new data using or sortField and sortDirection // e.g. https://api.github.com/search/repositories?q=blog&sort=${column.sortField}&order=${sortDirection} setSortField(column.sortField); setSortDirection(sortDirection); // alert(column); // alert(column.sortField); // alert(sortDirection); }; const handlePageChange = async (page) => { setPageNumber(page); handleDeepLink(); }; const handlePerRowsChange = async (newPerPage, page) => { setPageSize(newPerPage); handleDeepLink(); }; async function filterData() { setLoader(true); if (configLoaded) { setLoader(true); if ( refFromAmount.current?.value && isNaN(parseInt(refFromAmount.current?.value)) ) { setLoader(false); showNotification({ title: "Error", color: "red", message: "From Amount should be a number", }); return; } else if ( refToAmount.current?.value && isNaN(parseInt(refToAmount.current?.value)) ) { setLoader(false); showNotification({ title: "Error", color: "red", message: "To Amount should be a number", }); return; } let url = `${config.paymentApiUrl}/payment-service/admin/api/v1/installment/failedpayments?`; if (fromdate) { url = url + `fromDate=${moment(fromdate).format("YYYY-MM-DD")}`; } if (todate) { url = url + `&toDate=${moment(todate).format("YYYY-MM-DD")}`; } if (refFromAmount.current?.value) { url = url + `&fromAmount=${refFromAmount.current?.value}`; } if (refToAmount.current?.value) { url = url + `&toAmount=${refToAmount.current?.value}`; } if (refUserId.current?.value) { url = url + `&userId=${refUserId.current?.value}`; } if (refEmail.current?.value) { url = url + `&email=${refEmail.current?.value}`; url = url?.replaceAll("+", "%2B"); } // if (status.length > 0) { // status.forEach((el) => { // url = url + `&statuses=${el}`; // }); // } if (selectedProducts.length > 0) { selectedProducts.forEach((el) => { url = url + `&products=${el}`; }); } url = url + `&pageNumber=${pageNumber}&pageSize=${pageSize}&sortField=${sortField}&sortDirection=${sortDirection}`; await ltcService .get(url) .then((res) => { setLoader(false); handleDeepLink(); setData(res.data.data); setTotalRows(res.data.pagination.total); setPageNumber(res.data.pagination.pageNumber); }) .catch((error) => { setLoader(false); }); // setData(response.data); // setTotalRows(response.data.length); } } const resetFilter = () => { setFilter(""); }; async function resetData() { setLoader(true); let url = `${config.paymentApiUrl}/payment-service/admin/api/v1/installment/failedpayments`; url = url + `?pageNumber=${1}&pageSize=${pageSize}&sortField=${"DESC"}&sortDirection=${"startTime"}`; const response = await ltcService .get(url) .then((response) => { setData(response.data.data); setTotalRows(response.data.pagination.total); setPageNumber(response.data.pagination.pageNumber); // setPaginationTotalRows(response.data.pagination.total); setLoader(false); }) .catch((e) => { setLoader(false); }); } const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false); const caseInsensitiveSort = (rowA, rowB) => { const a = rowA.errorCount; const b = rowB.errorCount; if (a > b) { return 1; } if (b > a) { return -1; } return 0; }; const columns = [ { name: ( <Tooltip label="User Id" gutter={10}> User Id </Tooltip> ), selector: (row: any) => row["userId"], cell: (row: any) => { const data = row.userId?.split("-"); const filterData = data && data[0]; const copyId = (id: string) => { navigator.clipboard .writeText(id) .then(() => setCopyLable("Copied!")); setTimeout(() => setCopyLable("Copy"), 2000); }; return ( <div style={{ display: "flex", justifyContent: "center", alignItems: "center", }} > <div style={{ minWidth: "65px" }}> <Tooltip label={row.userId} gutter={10}> <Anchor href={`${config.userAppUrl}/users/${row.userId}`} target="_self" > {filterData} </Anchor> </Tooltip> </div> <div> <Tooltip label={copyLabel} gutter={10}> <ActionIcon onClick={() => copyId(row.userId)}> <FontAwesomeIcon icon={faCopy} size="1x" type="button" style={{ color: "228be6", }} /> </ActionIcon> </Tooltip> </div> </div> ); }, sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 0.7, sortField: "userId", }, { name: ( <Tooltip label="Email" gutter={10}> Email </Tooltip> ), selector: (row: any) => row["email"], cell: (row: any) => ( <div> <Tooltip label={row["email"] != null ? row["email"] : "-"} gutter={10} > {row["email"] != null ? row["email"] : "-"} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 0.7, sortField: "email", }, { name: ( <Tooltip label="Product" gutter={10}> Product </Tooltip> ), selector: (row: any) => row["productName"], cell: (row: any) => ( <div> <Tooltip label={ row["productName"] != null ? row["productName"] : "-" } gutter={10} > <Anchor href={`/product/${row.productId}`} target="_self" > {row["productName"] != null ? row["productName"] : "-"} </Anchor> </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1, sortField: "productName", }, { name: ( <Tooltip label="Status" gutter={10}> Status </Tooltip> ), selector: (row: any) => row["status"], cell: (row: any) => ( <div> <Tooltip label={row["status"] != null ? row["status"] : "-"} gutter={10} > {row["status"] != null ? row["status"] : "-"} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 0.5, sortField: "status", }, { name: ( <Tooltip label="Provider Status" gutter={10}> Provider Status </Tooltip> ), selector: (row: any) => row["stripePaymentStatus"], cell: (row: any) => ( <div> <Tooltip label={ row["stripePaymentStatus"] != null ? row["stripePaymentStatus"] : "-" } gutter={10} > {row["stripePaymentStatus"] != null ? row["stripePaymentStatus"] : "-"} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 0.5, sortField: "stripePaymentStatus", }, { name: ( <Tooltip label="Provider Failure Reason" gutter={10}> Provider Failure Reason </Tooltip> ), selector: (row: any) => row.failureReason, cell: (row: any) => ( <div> <Tooltip label={ row["failureReason"] != null ? row["failureReason"] : "-" } gutter={10} > {row["failureReason"] != null ? row["failureReason"] : "-"} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1.2, wrap: true, sortField: "reason", }, { name: ( <Tooltip label="Provider" gutter={10}> Provider </Tooltip> ), selector: (row: any) => "stripe", cell: (row: any) => ( <div> <Tooltip label={"stripe"} gutter={10}> stripe </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1.2, wrap: true, }, { name: ( <Tooltip label="Price" gutter={10}> Price </Tooltip> ), selector: (row: any) => row["formattedPrice"], cell: (row: any) => ( <div> <Tooltip label={ row["formattedPrice"] != null ? row["formattedPrice"] : "-" } gutter={10} > {row["formattedPrice"] != null ? row["formattedPrice"] : "-"} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 0.3, sortField: "price", }, { name: ( <Tooltip label="Payment Due Date" gutter={10}> Payment Due Date </Tooltip> ), selector: (row: any) => row.paymentDueDate, cell: (row: any) => ( <div> <Tooltip label={ row["paymentDueDate"] != null ? row["paymentDueDate"] : "-" } gutter={10} > {row.paymentDueDate ? new Date(row.paymentDueDate) .toLocaleDateString("de-CH") .concat(" ") .concat( new Date( row.paymentDueDate, ).toLocaleTimeString("de-CH"), ) : "-"} </Tooltip> </div> ), sortable: "true", grow: 0.5, sortField: "paymentDueDate", }, ]; const emiColumns = [ { name: ( <Tooltip label="Installment Id" gutter={10}> Installment Id </Tooltip> ), selector: (row: any) => row.paymentInstallmentId, cell: (row: any) => ( <div> <Tooltip label={row.paymentInstallmentId} gutter={10}> {row.paymentInstallmentId} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1.4, wrap: true, }, { name: ( <Tooltip label="Subscription Id" gutter={10}> Subscription Id </Tooltip> ), selector: (row: any) => row.subscriptionId, cell: (row: any) => ( <div> <Tooltip label={row.subscriptionId} gutter={10}> {row.subscriptionId} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1.4, wrap: true, }, { name: ( <Tooltip label="Payment Due Date" gutter={10}> Payment Due Date </Tooltip> ), selector: (row: any) => row.paymentDueDate, cell: (row: any) => ( <Tooltip label={row.paymentDueDate} gutter={10}> {row.paymentDueDate !== null ? new Date(row.paymentDueDate) .toLocaleDateString("de-CH") .concat(" ") .concat( new Date( row.paymentDueDate, ).toLocaleTimeString("de-CH"), ) : ""} </Tooltip> ), sortable: "true", grow: 2, }, { name: ( <Tooltip label="Package Name" gutter={10}> Package Name </Tooltip> ), selector: (row: any) => row.packageName, cell: (row: any) => ( <div> <Tooltip label={row.packageName} gutter={10}> {row.packageName} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1.2, wrap: true, }, { name: ( <Tooltip label="Price" gutter={10}> Price </Tooltip> ), selector: (row: any) => row.price, cell: (row: any) => ( <div> <Tooltip label={row.price != null ? row.price + " €" : ""} gutter={10} > {row.price != null ? row.price + " €" : ""} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1, wrap: true, }, { name: ( <Tooltip label="status" gutter={10}> Status </Tooltip> ), selector: (row: any) => row.status, cell: (row: any) => ( <div> <Tooltip label={row.status} gutter={10}> {row.status} </Tooltip> </div> ), sortable: "true", headerStyle: { fontWeight: "bold" }, grow: 1, wrap: true, }, ]; const handleProductChange = (event: any) => { if (event && event.target && event.target.value) { setSelectedProducts(event.target.value); } else { setSelectedProducts(event); } }; const handlePackageChange = (event: any) => { if (event && event.target && event.target.value) { setSelectedPackages(event.target.value); } else { setSelectedPackages(event); } }; // BreadCrumb const items = [ { title: translations.breadCrumbs.adminHome, href: "/" }, { title: "Payment Backoffice", href: "/" }, { title: "Failed Payments", href: "/failedpayments" }, ].map((item, index) => ( <Anchor href={item.href} key={index}> {item.title} </Anchor> )); function handleDeepLink() { let url = router.asPath.includes("#") ? router.asPath.substring(0, router.asPath.indexOf("#")) : router.asPath; url = router.asPath.includes("?") ? router.asPath.substring(0, router.asPath.indexOf("?")) : router.asPath.substring( 0, router.asPath.indexOf("/failedpayments"), ); url = url + "?"; //sources //from date if (fromdate) { if (!url.includes("=")) { url = `${url}inputFromDate=${fromdate}`; } else if (url.includes("=")) { url = `${url}&inputFromDate=${fromdate}`; } } if (todate) { if (!url.includes("=")) { url = `${url}inputToDate=${todate}`; } else if (url.includes("=")) { url = `${url}&inputToDate=${todate}`; } } if (refFromAmount.current?.value) { if (!url.includes("=")) { url = `${url}inputFromAmount=${refFromAmount.current?.value}`; } else if (url.includes("=")) { url = `${url}&inputFromAmount=${refFromAmount.current?.value}`; } } if (refToAmount.current?.value) { if (!url.includes("=")) { url = `${url}inputToAmount=${refToAmount.current?.value}`; } else if (url.includes("=")) { url = `${url}&inputToAmount=${refToAmount.current?.value}`; } } if (refUserId.current?.value) { if (!url.includes("=")) { url = `${url}inputUserId=${refUserId.current?.value}`; } else if (url.includes("=")) { url = `${url}&inputUserId=${refUserId.current?.value}`; } } if (refEmail.current?.value) { if (!url.includes("=")) { url = `${url}inputEmail=${refEmail.current?.value}`; url = url?.replaceAll("+", "%2B"); } else if (url.includes("=")) { url = `${url}&inputEmail=${refEmail.current?.value}`; url = url?.replaceAll("+", "%2B"); } } selectedProducts?.forEach((element, index) => { if (!url.includes("=")) { if (index == 0) url = `${url}inputProducts=${element}`; else { url = `${url}&inputProducts=${element}`; } } else if (url.includes("=")) { url = `${url}&inputProducts=${element}`; } }); if (!url.includes("=")) { url = `${url}pagenumber=${pageNumber}&pagesize=${pageSize}&inputSortField=${sortField}&inputSortDirection=${sortDirection}`; } else if (url.includes("=")) { url = `${url}&pagenumber=${pageNumber}&pagesize=${pageSize}&inputSortField=${sortField}&inputSortDirection=${sortDirection}`; } router.replace("/failedpayments?", url); console.log(router); } function resetLink() { console.log(router); setPageNumber(1); setPageSize(100); // loadData(); } const onFilterChange = async (filter) => { await setFilter(filter); setPageNumber(1); }; const form = useForm({ initialValues: { source: "", date: "", amount: "", paymentStatus: "", invoiceNum: "", email: "", country: "", }, }); return ( <> <LoadingOverlay visible={loader}></LoadingOverlay> <Breadcrumbs separator="→">{items}</Breadcrumbs> <br /> <Title style={{ marginBottom: "25px", fontSize: "24px" }}> Failed Payments </Title> <Paper withBorder shadow="sm" radius="md" p="md"> <Grid style={{ paddingTop: "10px" }}> <Grid.Col span={5}> <TextInput icon={<IconSearch />} styles={{ input: { borderRadius: "20px" }, }} placeholder="Search" value={filter} onChange={(event) => onFilterChange(event.currentTarget.value) } rightSection={ filter && ( <IconCircleLetterX onClick={() => { resetFilter(); }} size="2rem" style={{ color: "#228be6", display: "block", cursor: "pointer", }} /> ) } /> </Grid.Col> <Grid.Col span={4}></Grid.Col> <Grid.Col span={2}> <MultiSelect data={[ { label: "Draft", value: "DRAFT" }, { label: "Published", value: "PUBLISHED" }, { label: "Deactivated", value: "DEACTIVATED" }, { label: "Freebie", value: "FREEBIE" }, ]} value={selectedStatuses} placeholder="Status" searchable clearable onChange={handleStatusChange} /> </Grid.Col> <Grid.Col span={1}> <Group position="right"> <ActionIcon onClick={() => { setOpenAddDialog(true); }} variant="transparent" > <Tooltip label="Add New Product" gutter={10}> {/* <Link href="/newMeditation" style={{ cursor: "pointer" }}> */} <CirclePlus size={36} color={"#228be6"} /> {/* </Link> */} </Tooltip> </ActionIcon> </Group> </Grid.Col> </Grid> <br /> {data?.length > 0 ? ( <DataTable highlightOnHover={true} columns={columns} data={data} defaultSortFieldId="updatedOn" defaultSortAsc={false} defaultSortDirection="desc" sortDirection="desc" customStyles={dataTableStyle} sortIcon={<CaretSortIcon />} paginationPerPage={pageSize} paginationTotalRows={totalRows} paginationDefaultPage={pageNumber} onChangePage={handlePageChange} onChangeRowsPerPage={handlePerRowsChange} pagination paginationServer sortServer onSort={handleSort} paginationResetDefaultPage={resetPaginationToggle} // optionally, a hook to reset pagination to page 1 selectableRowsNoSelectAll noDataComponent persistTableHead paginationRowsPerPageOptions={[200, 250, 300]} /> ) : ( <Alert icon={<AlertCircle size={16} />} title="No Payments Found" ></Alert> )} <Modal transition="fade" transitionDuration={600} transitionTimingFunction="ease" opened={openAddDialog} withCloseButton onClose={() => setOpenAddDialog(false)} size="md" title="Create new Product" radius="md" styles={{ header: { color: "#228be6" }, title: { color: "#228be6" }, }} > <TextInput placeholder="Product Name" label="Product Name" defaultValue={name} onChange={handleNameChange} error={nameError} required /> <br /> </Modal> </Paper> </> ); }; FailedPayments.getInitialProps = async () => { return {}; }; export default FailedPayments;