Skip to content

Instantly share code, notes, and snippets.

@Oluwasetemi
Last active July 29, 2024 21:40
Show Gist options
  • Save Oluwasetemi/bc61bbe862536e92e19d5078a6ebaa63 to your computer and use it in GitHub Desktop.
Save Oluwasetemi/bc61bbe862536e92e19d5078a6ebaa63 to your computer and use it in GitHub Desktop.
import Box from '@mui/material/Box'
import TableCell from '@mui/material/TableCell'
import TableRow from '@mui/material/TableRow'
import Typography from '@mui/material/Typography'
import React, { useCallback, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import Table from 'components/table/Table'
import { FilterState } from 'interfaces/filter'
import FilterDropdown from 'components/FilterDropdown'
import { activityColumns, headerStyle, cases } from 'data/loan-account'
import CustomPagination from 'components/table/customPagination'
import { isSameDay, isAfter, format, parseISO } from 'date-fns'
import {
LoanPayment,
preppedData as preppedDataType,
} from 'interfaces/loansAccount'
import { formatNumber } from 'helper/utils'
import { TableExportTypes } from 'interfaces/Table'
import { abbreviateLoanType } from '../loans/loansHome/loanTableHelper'
import DownloadButton from './downloadButton'
import generatePDF from 'helper/pdfUtils'
import LoanStatement from './loanStatement'
import { useAppSelector } from 'redux/hooks'
type ActivityTableType = {
data: LoanPayment[]
currency: string
}
const ActivityTable = ({ data, currency }: ActivityTableType) => {
const preppedData: preppedDataType[] = data
.map((paymentData) => {
const {
id,
dueDate,
actualPaymentDate,
paymentType,
currency,
amount,
isCredit,
description,
isConverted,
conversionAmount,
conversionRate,
conversionCurrency,
conversionTime,
companyLoans,
} = paymentData
const loanType = companyLoans?.loanType
const loanName = companyLoans?.creditStructureApproved?.loanName
const modifiedPayment = {
id,
date: dueDate,
actualPaymentDate,
paymentType,
currency,
amount,
credit: isCredit ? amount : 0,
debit: isCredit ? 0 : amount,
isCredit,
description,
loanType: loanType,
loanName: loanName,
isConverted,
conversionAmount,
conversionRate,
conversionCurrency,
conversionTime,
}
return modifiedPayment
})
// .filter((payment) => !payment.paymentType?.includes('Penalty'))
.sort((a, b) => {
const timeA =
a.isCredit && a.actualPaymentDate ? a.actualPaymentDate : a.date
const timeB =
b.isCredit && b.actualPaymentDate ? b.actualPaymentDate : b.date
return isSameDay(new Date(timeA), new Date(timeB))
? 0
: isAfter(new Date(timeA), new Date(timeB))
? 1
: -1
})
const exportCustomization = {
description: (value: string | number, data: any) => {
const { loanType, loanName, isCredit } = data
const debitDescription = abbreviateLoanType(loanType) + ': ' + loanName
return isCredit ? data[value] : debitDescription
},
}
// const latestActivity = preppedData[preppedData.length - 1]?.date
// Define the desired date format
const dateFormat = 'dd MMM, yyyy'
const endDate = preppedData[preppedData.length - 1]?.date
const startDate = preppedData[0]?.date
// Parse the original date string into a Date object
const originalStartDate = parseISO(startDate)
const originalEndDate = parseISO(endDate)
const [allData, setAllData] = useState<preppedDataType[]>(preppedData)
const [currentFilterState, setCurrentFilterState] = useState<FilterState>({
date: {
values: [],
},
})
const [exportFunction, setExportFunction] = useState(false)
const [exportType, setExportType] = useState<TableExportTypes>('pdf')
const [filteredData, setFilteredData] =
useState<preppedDataType[]>(preppedData)
const [balancedData, setBalancedData] =
useState<preppedDataType[]>(preppedData)
const [tableData, setTableData] = useState<preppedDataType[]>(preppedData)
const [totals, setTotals] = useState<{
credit: number
debit: number
balance: number
}>()
const tableStyles = {
overflow: 'auto',
'&::-webkit-scrollbar-track': {
backgroundColor: '#f5f5f5',
},
'&::-webkit-scrollbar-thumb': {
boxShadow: 'inset 0 0 2px rgba(0, 0, 0, 0.3)',
backgroundColor: '#F3F6FF',
borderRadius: '3px',
},
}
const [page, setPage] = useState<number>(0) // Pass page to table pagination prop e.g pagination={{ page }}
const [pagination, setPagination] = useState({
rowsPerPageValue: 5,
rowsPerPageOptions: [5, 10, 15],
})
const company = useAppSelector((state) => state.company.company)
const companyName = company?.legalName as string
const companyAddress = company.headquartersCity as string
const handleGeneratePDF = async () => {
await generatePDF(
<LoanStatement
data={tableData || []}
credit={formatNumber().format(totals?.credit as number)}
debit={formatNumber().format(totals?.debit as number)}
outstandingBalance={formatNumber().format(totals?.balance as number)}
getData={apiDataResource}
currency={currency}
companyName={companyName}
companyAddress={companyAddress}
endDate={format(originalEndDate, dateFormat)}
startDate={format(originalStartDate, dateFormat)}
/>,
`Loan account statement - ${companyName}`
)
}
const handleGenerateCSV = () => {
console.log(tableData)
const csvData = tableData.map((data) => {
const {
date,
paymentType,
description,
currency,
amount,
credit,
debit,
loanType,
loanName,
} = data
return {
Date: format(new Date(date), 'dd MMM yyyy'),
'Payment Type': paymentType,
Description: description,
Currency: currency,
Amount: amount,
Credit: credit,
Debit: debit,
'Loan Type': loanType,
'Loan Name': loanName,
}
})
const csvHeaders = [
'Date',
'Payment Type',
'Description',
'Currency',
'Amount',
'Credit',
'Debit',
'Loan Type',
'Loan Name',
]
const csvDataFormatted = csvData.map((data) => {
return csvHeaders.map((header) => data[header])
})
const generateSummary = () => {
// const credit = data.reduce((acc, curr) => acc + curr.Credit, 0)
// const debit = data.reduce((acc, curr) => acc + curr.Debit, 0)
// const balance = credit - debit
return {
'Total Inflows': totals?.credit || 0,
'Total Outflows': totals?.debit || 0,
'Ending Outstanding Balance': totals?.balance || 0,
}
}
const turnSummaryIntoCSV = (summary: any) => {
return Object.entries(summary).map(([key, value]) => {
return `${key},,${value}`
})
}
const summary = generateSummary()
const summaryCSV = turnSummaryIntoCSV(summary)
const csvDataString = csvDataFormatted
.map((data) => data.join(','))
.join('\n')
// join the summary data with the csv data
const csvDataStringWithSummary =
`
,,,,,,,,,,,
,,${companyName}'s Payment Schedule,,,,,,,,,07/08/2024
,,(Updated as of ${format(new Date(), 'dd MMM yyyy')}),,,,,,,,,
Summary,,,,,"If you have any questions, please reach out to:",,,,,,,,,,
,,,,,,[email protected],,,,,,,
` +
summaryCSV.join('\n') +
'\n'.repeat(3) +
csvHeaders.join(',') +
'\n' +
csvDataString
console.log(csvDataStringWithSummary)
const blob = new Blob([csvDataStringWithSummary], {
type: 'text/csv;charset=utf-8;',
})
saveAs(blob, `Loan account statement - ${companyName}.csv`)
}
function saveAs(blob: Blob, fileName: string) {
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
}
// This should be passed as part of the pagination prop of the table. e.g pagination={{ onPageChange }}
const onPageChange = (_: unknown, page: number) => {
setPage(page)
}
// This should be passed as part of the pagination prop of the table e.g pagination={{ onRowsPerPageChange }}
const onRowsPerPageChange = (value: number | string) => {
setPagination((prevValue) => ({
...prevValue,
rowsPerPageValue: typeof value === 'string' ? parseInt(value, 10) : value,
}))
setPage(0)
}
const exportFunctionality = (label: TableExportTypes) => {
setExportType(label)
setExportFunction(true)
setTimeout(() => {
setExportFunction(false)
}, 200)
}
const apiDataResource = useCallback(
async (rowsPerPage = pagination.rowsPerPageValue, page = 0) => {
const statements = balancedData ? [...balancedData] : []
const startIndex = statements.length - page * rowsPerPage
const endIndex = statements.length - (page + 1) * rowsPerPage
const displayData = statements.filter(
(e: any, i: any) => i < startIndex && i >= endIndex
)
setTableData([...displayData] || [])
},
[balancedData]
)
const filterTableData = () => {
const filterDates = currentFilterState?.date?.values[0]?.value as string
if (filterDates && filterDates !== 'all') {
const startDate = filterDates.split(', ')[1]
const endDate = filterDates.split(', ')[0]
const modifiedTableData = allData.filter((loanPayment) => {
const { isCredit, actualPaymentDate, date } = loanPayment
const dateToFilter =
isCredit && actualPaymentDate ? actualPaymentDate : date
return (
isAfter(new Date(dateToFilter), new Date(startDate)) &&
isAfter(new Date(endDate), new Date(dateToFilter))
)
})
setFilteredData(modifiedTableData)
} else {
setFilteredData(allData)
}
}
const balanceTableData = () => {
let credit = 0
let debit = 0
let balance = 0
const modifiedTableData = filteredData.reduce(
(accumulator, currentValue) => {
balance =
balance +
(currentValue.isCredit
? currentValue.amount * -1
: currentValue.amount)
if (currentValue.isCredit) {
credit += currentValue.amount
} else {
debit += currentValue.amount
}
currentValue['outstandingBalance'] = balance
return [...accumulator, currentValue]
},
[] as preppedDataType[]
)
const total = { credit, debit, balance }
setBalancedData(modifiedTableData)
setTotals(total)
}
useEffect(() => {
filterTableData()
setPage(0)
setPagination({
rowsPerPageValue: 5,
rowsPerPageOptions: [5, 10, 15],
})
}, [currentFilterState])
useEffect(() => {
balanceTableData()
}, [filteredData])
useEffect(() => {
apiDataResource()
}, [apiDataResource])
useEffect(() => {
setAllData(preppedData)
}, [data.length])
return (
<Box>
<Helmet title="Fluna Inc. | Your Loan Home" />
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
margin: '1rem 0 2rem 0',
}}
>
<Typography
sx={{
fontWeight: 600,
fontSize: '1.5rem',
lineHeight: '2rem',
color: '#171721',
}}
>
Activity to Date
</Typography>
</Box>
<Box
sx={{
gap: 2,
display: 'flex',
alignItems: 'center',
}}
>
<FilterDropdown
filterValues={{
name: 'date',
selectType: 'date',
options: [
{ name: 'Today', value: 'today' },
{ name: 'This Month', value: 'this_month' },
{ name: 'Last Month', value: 'last_month' },
{ name: 'Custom Date', value: 'custom_date' },
],
}}
filterState={currentFilterState}
setFilterState={setCurrentFilterState}
/>
<DownloadButton
downloadPdf={handleGeneratePDF}
downloadCsv={handleGenerateCSV}
exportFunctionality={exportFunctionality}
/>
</Box>
</Box>
<Box
sx={{
border: '1px solid #E9EAEF',
padding: '18px 0 0 0px',
borderRadius: '8px',
}}
>
<Table<any>
data={tableData || []}
totalRows={balancedData?.length}
columns={activityColumns}
exporType={exportType}
exportFunction={exportFunction}
footerStyle={{ background: '#F3F6FF' }}
pagination={{
show: false,
page,
onRowsPerPageChange,
onPageChange,
customPagination: false,
rowsPerPageValue: pagination.rowsPerPageValue,
rowsPerPageOptions: pagination.rowsPerPageOptions,
}}
footerInfo={{
data: {
date: 'Total Balance',
paymentType: '---',
description: '---',
credit: formatNumber().format(totals?.credit as number),
debit: formatNumber().format(totals?.debit as number),
outstandingBalance: formatNumber().format(
totals?.balance as number
),
},
Footer: (props: any) => {
const data = props?.data
return (
<TableRow sx={{ background: '#F3F6FF' }}>
<TableCell
sx={{
fontWeight: 400,
fontSize: '0.875rem',
lineHeight: '1rem',
color: '#171721',
}}
>
{data?.date}
</TableCell>
<TableCell
sx={{
fontWeight: 400,
fontSize: '0.875rem',
lineHeight: '1rem',
color: '#8083A3',
}}
>
{data?.paymentType}
</TableCell>
<TableCell
sx={{
fontWeight: 400,
fontSize: '0.875rem',
lineHeight: '1rem',
color: '#8083A3',
}}
>
{data?.description}
</TableCell>
<TableCell>
<Box
component={'span'}
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
color: data?.credit === '0' ? '#8083A3' : '#171721',
}}
>
{data?.credit === '0'
? '---'
: `${currency}${data?.credit}`}
</Box>
</TableCell>
<TableCell>
<Box
component={'span'}
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
fontWeight: data?.debit === '0' ? 400 : 600,
fontSize: '0.875rem',
lineHeight: '1.25rem',
color: data?.debit === '0' ? '#8083A3' : '#D14343',
}}
>
{data?.debit === '0'
? '---'
: `${currency}${data?.debit}`}
</Box>
</TableCell>
<TableCell>
<Box
component={'span'}
sx={{
width: '100%',
display: 'flex',
justifyContent: 'flex-end',
fontWeight: 600,
fontSize: '0.875rem',
lineHeight: '1.25rem',
color:
data?.outstandingBalance <= 0 ? '#2952CC' : '#171721',
}}
>
{currency}
{data?.outstandingBalance}
</Box>
</TableCell>
</TableRow>
)
},
}}
height={tableData.length < 7 ? '100%' : '20rem'}
sorting={true}
loadingData={false}
RenderedModal={() => null}
getData={apiDataResource}
customizations={{ cases: cases(currency) }}
headerStyle={headerStyle}
tableStyles={tableStyles}
showTooltip={true}
exportCustomization={exportCustomization}
modifiedExportName={`Loan account statement - ${company.name}`}
/>
<CustomPagination
page={page}
count={balancedData?.length}
rowsPerPage={pagination.rowsPerPageValue}
onRowsPerPageChange={onRowsPerPageChange}
rowsPerPageOptions={pagination.rowsPerPageOptions}
onPageChange={onPageChange}
/>
</Box>
</Box>
)
}
export default ActivityTable
import Download from '@mui/icons-material/Download'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import { MenuProps } from '@mui/material'
import { styled } from '@mui/material/styles'
import { TableExportTypes } from 'interfaces/Table'
import React, { useState } from 'react'
interface IDownloadButton {
downloadPdf: () => void
downloadCsv?: () => void
exportFunctionality: (label: TableExportTypes) => void
}
const StyledMenu = styled((props: MenuProps) => (
<Menu
elevation={0}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
{...props}
/>
))(() => ({
'& .MuiPaper-root': {
marginTop: '.2rem',
borderRadius: 6,
minWidth: 180,
paddingTop: '.6rem',
paddingBottom: '.6rem',
boxShadow: '0px',
border: '1px solid #BBBEC2',
'& .MuiMenu-list': {
padding: '4px 0',
},
'& .MuiMenuItem-root': {
fontSize: '.75rem',
fontWeight: 400,
lineHeight: '1rem',
color: '#171721',
'& span': {
color: '#8083A3',
},
'&:active, &:hover': {
backgroundColor: '#FEF7F1',
},
},
},
}))
const DownloadButton = ({
downloadPdf,
downloadCsv,
}: // exportFunctionality,
IDownloadButton) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const open = Boolean(anchorEl)
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
return (
<>
<Button
id="download-statement"
aria-controls={open ? 'download-statement-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
variant="contained"
disableElevation
onClick={handleClick}
endIcon={<Download />}
sx={{
py: '.5rem',
fontFamily: 'SF Pro Display Medium',
fontSize: '.75rem',
fontWeight: 500,
lineHeight: '20px',
borderRadius: '8px',
backgroundColor: 'transparent',
border: '1px solid #BBBEC2',
'&:hover': {
boxShadow: 0,
background: 'transparent',
},
}}
>
Download Statement
</Button>
<StyledMenu
id="download-statement-menu"
MenuListProps={{
'aria-labelledby': 'download-statement',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
<MenuItem
onClick={() => {
handleClose()
downloadPdf()
}}
disableRipple
>
Download as PDF <Box component="span">(.pdf)</Box>
</MenuItem>
<MenuItem
onClick={() => {
handleClose()
downloadCsv && downloadCsv()
}}
disableRipple
>
Download as CSV <Box component="span">(.csv)</Box>
</MenuItem>
</StyledMenu>
</>
)
}
export default DownloadButton
@Oluwasetemi
Copy link
Author

Oluwasetemi commented Jul 29, 2024

const handleGenerateCSV = () => {
    // create an array of credit, debit and balance
    const creditArray: number[] = []
    const debitArray: number[] = []
    const balanceArray: number[] = []

    // console.log(creditArray, debitArray, balanceArray)

    // map the needed data Array of Object with the Table heading as the key
    const csvData = tableData.map((data) => {
      const { date, paymentType, description, credit, debit, amount } = data

      creditArray.push(credit)
      debitArray.push(debit)
      balanceArray.push(amount)

      return {
        DATE: format(new Date(date), 'dd MMM yyyy'),
        'PAYMENT TYPE': paymentType,
        DESCRIPTION: description,
        CREDIT: credit,
        DEBIT: debit,
        'OUT. BALANCE': amount,
      }
    })

    // get the headers of the csv data
    const csvHeaders = Object.keys(csvData[0])

    const csvDataFormatted = csvData.map((data) => {
      return csvHeaders.map((header) => data[header])
    })

    const creditTotal = creditArray.reduce((acc, curr) => acc + curr, 0)
    const debitTotal = debitArray.reduce((acc, curr) => acc + curr, 0)
    const balanceTotal = balanceArray.reduce((acc, curr) => acc + curr, 0)

    // generate the summary of the csv data
    // NB: The summary is the total of the credit, debit and balance and a fallback to the totals
    const generateSummary = () => {
      return {
        'Total Inflows': creditTotal || totals?.credit || 0,
        'Total Outflows': debitTotal || totals?.debit || 0,
        'Ending Out. Balance': balanceTotal || totals?.balance || 0,
      }
    }

    const turnSummaryIntoCSV = (summary: any) => {
      return Object.entries(summary).map(([key, value], index) => {
        return index === 0
          ? `,,${key},${value},,,[email protected],,,,`
          : `,,${key},${value}`
      })
    }
    // console.log(turnSummaryIntoCSV)

    const summary = generateSummary()
    // console.log(summary)
    const summaryCSV = turnSummaryIntoCSV(summary)
    // console.log(summaryCSV)

    const csvDataString = csvDataFormatted
      .map((each) => [',', ...each])
      .map((data) => data.join(','))
      .join('\n')

   const totalSummary = {
      date: 'Total Balance',
      paymentType: '---',
      description: '---',
      credit: `$${creditTotal}`,
      debit: `$${debitTotal}`,
      amount: `$${balanceTotal}`,
    }
    // join the summary data with the csv data
    const csvDataStringWithSummary = `
    ,,,,,,,,,,,
    ,,,,,,,,,,,
    ,,${companyName}'s Payment Schedule,,,,,,,,,
    ,,(Updated as of ${format(new Date(), 'dd MMM yyyy')}),,,,,,,,
    ,,,,,,,,,,
    ,,Summary,,,,"If you have any questions, please reach out to:",,,,
    ${summaryCSV.join('\n')}
    ,,,,,,,,,,
    ,,,,,,,,,,
    ,,,,,,,,,,
    ,,,,,,,,,,
    ,,${csvHeaders.join(',') + '\n' + csvDataString}
    ,,,,,,,,,,,,,,,
    ${[',', ...[Object.values(totalSummary)]].join(',')}
    `

    // console.log(csvDataStringWithSummary)
    const blob = new Blob([csvDataStringWithSummary], {
      type: 'text/csv;charset=utf-8;',
    })

    saveAs(blob, `Loan_account_statement(csv)-${companyName}.csv`)
  }

  function saveAs(blob: Blob, fileName: string) {
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = fileName
    a.click()
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment