'use client'; import { useState, useCallback, useEffect, useMemo } from 'react'; // @mui import { useTheme, alpha } from '@mui/material/styles'; import Tab from '@mui/material/Tab'; import Tabs from '@mui/material/Tabs'; import Card from '@mui/material/Card'; import Table from '@mui/material/Table'; import Stack from '@mui/material/Stack'; import Button from '@mui/material/Button'; import Divider from '@mui/material/Divider'; import Container from '@mui/material/Container'; import TableBody from '@mui/material/TableBody'; import TableContainer from '@mui/material/TableContainer'; // routes import { paths } from 'src/routes/paths'; import { useRouter } from 'src/routes/hooks'; import { RouterLink } from 'src/routes/components'; // utils import { fTimestamp } from 'src/utils/format-time'; // components import Label from 'src/components/label'; import Iconify from 'src/components/iconify'; import Scrollbar from 'src/components/scrollbar'; import { useSettingsContext } from 'src/components/settings'; import CustomBreadcrumbs from 'src/components/custom-breadcrumbs'; import { useTable, getComparator, emptyRows, TableNoData, TableEmptyRows, TableHeadCustom, TablePaginationCustom, } from 'src/components/table'; // types import { Invoice, InvoiceStatus, InvoiceTableFilters, InvoiceTableFilterValue } from 'src/schemas'; // import deleteFromFirebaseStorage from 'src/utils/delete-from-firebase-storage'; // fetch import { useDeleteInvoice, useGetInvoices } from 'src/api/invoice'; import { collections, removeDocument } from 'src/lib/firestore'; import { mutate } from 'swr'; import { useBoolean } from 'src/hooks/use-boolean'; import InvoiceAnalytic from '../invoice-analytic'; import InvoiceTableRow from '../invoice-table-row'; import InvoiceTableFiltersResult from '../invoice-table-filters-result'; import InvoiceTableToolbar from '../invoice-table-toolbar'; import MailCompose from '../mail-compose'; import { useFetchAnalytics } from 'src/api/invoice/use-fetch-analytics'; import { endpoints } from 'src/utils/axios'; // ---------------------------------------------------------------------- const TABLE_HEAD = [ { id: 'invoiceNumber', label: 'Customer' }, { id: 'price', label: 'Amount' }, { id: 'currency', label: 'Currency' }, { id: 'invoicePeriod', label: 'Invoice Period' }, { id: 'issueDate', label: 'Create' }, { id: 'dueDate', label: 'Due' }, { id: 'sent', label: 'Sent', align: 'center' }, { id: 'status', label: 'Status' }, { id: '' }, ]; const defaultFilters: InvoiceTableFilters = { name: '', service: [], status: 'all', startDate: new Date('2024-01-01T00:00:00Z'), endDate: null, }; // ---------------------------------------------------------------------- interface StatusTotals { EUR: number; USD: number; } type ResultsType = { [key in 'total' | InvoiceStatus]: StatusTotals; }; const getEURtoUSDExchangeRate = async () => { try { // const response = await fetch( // 'http://api.exchangeratesapi.io/v1/latest?access_key=248cc5d9f103fcb183ed4987f2d85b3b&base=EUR&symbols=USD' // ); // const data = await response.json(); // return data.rates.USD; return 1.05; } catch (error) { console.error('Error fetching the exchange rate:', error); return null; } }; const getTotalAmountForAllStatuses = async (invoices: Invoice[]) => { const eurToUsdRate = await getEURtoUSDExchangeRate(); if (!eurToUsdRate) { throw new Error("Couldn't fetch the exchange rate."); } const results: ResultsType = { total: { EUR: 0, USD: 0 }, // excluded processing: { EUR: 0, USD: 0 }, paid: { EUR: 0, USD: 0 }, pending: { EUR: 0, USD: 0 }, overdue: { EUR: 0, USD: 0 }, draft: { EUR: 0, USD: 0 }, }; invoices.forEach((invoice) => { const updateAmounts = (key: 'total' | InvoiceStatus) => { const excludeProcessing = key === 'processing' ? 'pending' : key; if (invoice.currency === 'USD') { results[excludeProcessing].USD += invoice.totalAmount; results[excludeProcessing].EUR += invoice.totalAmount / eurToUsdRate; // Convert USD to EUR } else if (invoice.currency === 'EUR') { results[excludeProcessing].EUR += invoice.totalAmount; results[excludeProcessing].USD += invoice.totalAmount * eurToUsdRate; // Convert EUR to USD } }; updateAmounts('total'); updateAmounts(invoice.status as InvoiceStatus); }); return results; }; export default function InvoiceListView() { const theme = useTheme(); const settings = useSettingsContext(); const [analytics, setAnalytics] = useState({ total: { EUR: 0, USD: 0 }, paid: { EUR: 0, USD: 0 }, pending: { EUR: 0, USD: 0 }, overdue: { EUR: 0, USD: 0 }, draft: { EUR: 0, USD: 0 }, }); const [sendingInvoice, setSendingInvoice] = useState(null); const router = useRouter(); const table = useTable({ defaultOrderBy: 'issueDate', defaultOrder: 'desc', defaultDense: true, defaultRowsPerPage: 25, }); const [filters, setFilters] = useState(defaultFilters); const { invoices: tableData } = useGetInvoices({ startDate: filters.startDate?.toISOString() }); const invoiceMutationKey = useMemo( () => [ collections.invoice, JSON.stringify({ where: [['issueDate', '>=', filters.startDate]], orderBy: 'issueDate', direction: 'desc', }), ], [filters] ); const dateError = filters.startDate && filters.endDate ? filters.startDate.getTime() > filters.endDate.getTime() : false; const dataFiltered = applyFilter({ inputData: tableData, comparator: getComparator(table.order, table.orderBy), filters, dateError, }); // const dataInPage = dataFiltered.slice( // table.page * table.rowsPerPage, // table.page * table.rowsPerPage + table.rowsPerPage // ); const denseHeight = table.dense ? 56 : 76; const canReset = !!filters.name || !!filters.service.length || filters.status !== 'all' || (!!filters.startDate && !!filters.endDate); const notFound = (!dataFiltered.length && canReset) || !dataFiltered.length; const getInvoiceLength = (status: string) => tableData.filter((item) => item.status === status).length; // const getTotalAmount = (status: string) => // sumBy( // tableData.filter((item) => item.status === status), // 'totalAmount' // ); const { analytics: analyticsData, isAnalyticsLoading, analyticsError, } = useFetchAnalytics(filters.startDate); useEffect(() => { if (analyticsData) { setAnalytics(analyticsData); } }, [analyticsData]); useEffect(() => { if (analyticsError) { console.error('Failed to load analytics:', analyticsError); } }, [analyticsError]); const getPercentByStatus = (status: string) => (getInvoiceLength(status) / tableData.length) * 100; const TABS = [ { value: 'all', label: 'All', color: 'default', count: tableData.length }, { value: 'paid', label: 'Paid', color: 'success', count: getInvoiceLength('paid'), }, { value: 'pending', label: 'Pending', color: 'warning', count: getInvoiceLength('pending'), }, { value: 'overdue', label: 'Overdue', color: 'error', count: getInvoiceLength('overdue'), }, { value: 'draft', label: 'Draft', color: 'default', count: getInvoiceLength('draft'), }, ] as const; const handleFilters = useCallback( (name: string, value: InvoiceTableFilterValue) => { table.onResetPage(); setFilters((prevState) => ({ ...prevState, [name]: value, })); }, [table] ); const { deleteInvoiceMutation } = useDeleteInvoice(); const handleDeleteRow = useCallback( async (invoice: Invoice) => { const serializedParams = JSON.stringify({ where: [['issueDate', '>=', filters.startDate]], orderBy: 'issueDate', direction: 'desc', }); await deleteInvoiceMutation(invoice.id); await deleteFromFirebaseStorage( `invoices/${invoice.invoiceTo.name}-${invoice.invoiceNumber}.pdf` ); mutate(endpoints.invoice); }, [filters.startDate, invoiceMutationKey] ); const openCompose = useBoolean(); const handleToggleCompose = useCallback( async (invoice: Invoice) => { setSendingInvoice(invoice); openCompose.onToggle(); }, [openCompose] ); // const handleDeleteRows = useCallback(() => { // const deleteRows = tableData.filter((row) => !table.selected.includes(row.id)); // // setTableData(deleteRows); // table.onUpdatePageDeleteRows({ // totalRows: tableData.length, // totalRowsInPage: dataInPage.length, // totalRowsFiltered: dataFiltered.length, // }); // }, [dataFiltered.length, dataInPage.length, table, tableData]); const handleEditRow = useCallback( (id: string) => { router.push(paths.dashboard.invoice.edit(id)); }, [router] ); const handleCopyRow = useCallback( (id: string) => { router.push(paths.dashboard.invoice.copy(id)); }, [router] ); const handleViewRow = useCallback( (id: string) => { router.push(paths.dashboard.invoice.details(id)); }, [router] ); const handleFilterStatus = useCallback( (event: React.SyntheticEvent, newValue: string) => { handleFilters('status', newValue); }, [handleFilters] ); const handleResetFilters = useCallback(() => { setFilters(defaultFilters); }, []); if (isAnalyticsLoading) { // Show loading state } return ( <> } > New Invoice } sx={{ mb: { xs: 3, md: 5 }, }} /> } sx={{ py: 2 }} > {TABS.map((tab) => ( {tab.count} } /> ))} {canReset && ( )} {/* table.onSelectAllRows( checked, tableData.map((row) => row.id) ) } action={ } /> */} table.onSelectAllRows( checked, tableData.map((row) => row.id) ) } /> {dataFiltered .slice( table.page * table.rowsPerPage, table.page * table.rowsPerPage + table.rowsPerPage ) .map((row) => ( table.onSelectRow(row.id)} onViewRow={() => handleViewRow(row.id)} onEditRow={() => handleEditRow(row.id)} onCopyRow={() => handleCopyRow(row.id)} onDeleteRow={async () => handleDeleteRow(row)} onSendRow={() => {}} onToggleCompose={() => handleToggleCompose(row)} /> ))}
{openCompose.value && ( )} {/* Are you sure want to delete {table.selected.length} items? } action={ } /> */} ); } // ---------------------------------------------------------------------- function applyFilter({ inputData, comparator, filters, dateError, }: { inputData: Invoice[]; comparator: (a: any, b: any) => number; filters: InvoiceTableFilters; dateError: boolean; }) { const { name, status, service, startDate, endDate } = filters; const stabilizedThis = inputData.map((el, index) => [el, index] as const); stabilizedThis.sort((a, b) => { const order = comparator(a[0], b[0]); if (order !== 0) return order; return a[1] - b[1]; }); inputData = stabilizedThis.map((el) => el[0]); if (name) { inputData = inputData.filter( (invoice) => invoice.invoiceNumber.toLowerCase().indexOf(name.toLowerCase()) !== -1 || invoice.invoiceTo.name.toLowerCase().indexOf(name.toLowerCase()) !== -1 ); } if (status !== 'all') { inputData = inputData.filter((invoice) => invoice.status === status); } if (service.length) { inputData = inputData.filter((invoice) => invoice.items.some((filterItem) => service.includes(filterItem.service.id)) ); } if (!dateError) { if (startDate && endDate) { inputData = inputData.filter( (invoice) => fTimestamp(invoice.issueDate.getTime()) >= fTimestamp(startDate.getTime()) && fTimestamp(invoice.issueDate.getTime()) <= fTimestamp(endDate.getTime()) ); } } return inputData; }