mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-18 12:39:18 +00:00
* feat: initial SPA setup for banking * wip: bring over new banking module * feat: added Espresso design tokens * feat: button styles * fix: add all ink colors * wip: espresso design system changes * feat: button and badge espresso components * fix: button styling for reconcile * feat: Espresso progress bar * feat: Espresso toggle switch * feat: Espresso tabs design * fix: vertical tab support * fix: button sizing across modals * feat: Espresso style table layout * feat: Espresso tooltip * feat: Espresso elevations and checkbox * feat: Dialog with Espresso styles * feat: Espresso textarea * fix: input styles * fix: colors on bank picker * fix: breadcrumb styling * fix: bank picker styling * feat: create doctypes and fields for bank reconciliation * feat: APIs for banking * fix: use date format parser * fix: font styling to match Espresso * wip: settings modal * feat: settings dialog component * fix: icons and invalid requests * feat: preferences tab * fix: adjust icon stroke width to 1.5 * feat: rule configuration in settings * fix: remove sheet component * feat: alert and error banner component * feat: dropdown in Espresso * feat: popover and select in Espresso * fix: cleanup more styles * fix: match size of link fields * feat: command styling * fix: remove unused style tokens * fix: styles for global date picker dropdown * fix: styles for match and reconcile * feat: table Espresso component * feat: remove all other design tokens * fix: remove unused tokens * fix: form elements * fix: remove unused styles and fix filters in bank transaction list * feat: fetch bank rec doctypes for filtering * fix: record payment modal * feat: support for dark mode switching * fix: move bank logos to public folder * feat: add support for RTL * feat: support for RTL * chore: send layout direction in dev boot * fix: make checkbox work in RTL * feat: dark mode support * fix: dark mode style * feat: bank logos in dark mode * feat: dark mode bank logos * chore: use dark mode bank logos everywhere * chore: move rule evaluation to controller * chore: add tests for bank transaction rules * fix: move deps to fix actions errors * fix: move tw-animate-css to deps * fix: remove shadcn * fix: do not open modal if no transactions selected * fix: add translation strings * feat: add banner on existing bank reconciliation tool * feat: bank statement import * fix: translations and layout directions * fix: validation for transaction matching rule * fix: styles * fix: show conflicting transactions in alert * fix: show help text for new banking module forms * feat: show total debits and credits * fix: dark mode colors in automatic config * feat: add keyboard shortcuts help * feat: added keyboard shortcut for settings * fix: decrease size of progress bar * chore: bump packages * feat: add tests for statement import * fix: settings dialog * fix: show banner on small screens * fix: show banner when no bank account set
475 lines
23 KiB
TypeScript
475 lines
23 KiB
TypeScript
import { Button } from '@/components/ui/button'
|
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
|
import _ from '@/lib/translate'
|
|
import { useAtomValue, useSetAtom } from 'jotai'
|
|
import { ArrowDownRight, ArrowRightLeftIcon, ArrowUpRight, CalendarIcon, CircleXIcon, GitCompareIcon, HistoryIcon, LandmarkIcon, Loader2Icon, ReceiptIcon, ReceiptTextIcon, UserIcon, WalletIcon } from 'lucide-react'
|
|
import { useMemo, useState } from 'react'
|
|
import { ActionLogItem, ActionLog as ActionLogType, bankRecActionLog, bankRecDateAtom, bankRecMatchFilters, SelectedBank, selectedBankAccountAtom } from '../BankReconciliation/bankRecAtoms'
|
|
import { useHotkeys } from 'react-hotkeys-hook'
|
|
import { useGetBankAccounts } from '../BankReconciliation/utils'
|
|
import { getCompanyCurrency } from '@/lib/company'
|
|
import { formatCurrency } from '@/lib/numbers'
|
|
import dayjs from 'dayjs'
|
|
import { cn } from '@/lib/utils'
|
|
import { formatDate } from '@/lib/date'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { slug } from '@/lib/frappe'
|
|
import { PaymentEntry } from '@/types/Accounts/PaymentEntry'
|
|
import { JournalEntry } from '@/types/Accounts/JournalEntry'
|
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
|
|
import { Table, TableCell, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
|
import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'
|
|
import { useFrappePostCall, useSWRConfig } from 'frappe-react-sdk'
|
|
import { toast } from 'sonner'
|
|
import { getErrorMessage } from '@/lib/frappe'
|
|
import ErrorBanner from '@/components/ui/error-banner'
|
|
import SelectedTransactionDetails from '../BankReconciliation/SelectedTransactionDetails'
|
|
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'
|
|
import BankLogo from '@/components/common/BankLogo'
|
|
|
|
const ActionLog = () => {
|
|
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
|
|
useHotkeys('meta+z', () => {
|
|
setIsOpen(true)
|
|
}, {
|
|
enabled: true,
|
|
enableOnFormTags: false,
|
|
preventDefault: true
|
|
})
|
|
|
|
return (
|
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<DialogTrigger asChild>
|
|
<Button variant={'outline'} isIconButton size='md'>
|
|
<HistoryIcon />
|
|
</Button>
|
|
</DialogTrigger>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
{_("Reconciliation History")}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<DialogContent className='min-w-[90vw]'>
|
|
<DialogHeader>
|
|
<DialogTitle>{_("Reconciliation History")}</DialogTitle>
|
|
<DialogDescription>{_("View all reconciliation actions taken in this session.")}</DialogDescription>
|
|
</DialogHeader>
|
|
<ActionLogDialogContent />
|
|
<DialogFooter>
|
|
<DialogClose asChild>
|
|
<Button variant={'outline'} size='md' onClick={() => setIsOpen(false)}>{_("Close")}</Button>
|
|
</DialogClose>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|
|
|
|
const ActionLogDialogContent = () => {
|
|
|
|
const actionLog = useAtomValue(bankRecActionLog)
|
|
|
|
return <div className='flex flex-col gap-2'>
|
|
{actionLog.map((action) => (
|
|
<div key={action.timestamp} className='flex flex-col gap-1'>
|
|
<ActionGroupHeader action={action} />
|
|
<div>
|
|
<div className='ms-2 border-s border-s-outline-gray-2 py-1'>
|
|
<div className='ms-5'>
|
|
{action.items.map((item, index) => (
|
|
<Row
|
|
item={item}
|
|
key={item.bankTransaction.name}
|
|
index={index}
|
|
action={action}
|
|
isLast={index === action.items.length - 1} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{actionLog.length === 0 && <Empty>
|
|
<EmptyMedia>
|
|
<HistoryIcon />
|
|
</EmptyMedia>
|
|
<EmptyHeader>
|
|
<EmptyTitle>{_("No reconciliation actions found")}</EmptyTitle>
|
|
<EmptyDescription>{_("You have not performed any reconciliations in this session yet.")}</EmptyDescription>
|
|
</EmptyHeader>
|
|
</Empty>}
|
|
</div>
|
|
}
|
|
|
|
|
|
|
|
const ActionGroupHeader = ({ action }: { action: ActionLogType }) => {
|
|
|
|
const label = useMemo(() => {
|
|
switch (action.type) {
|
|
case 'match':
|
|
return _("Matched")
|
|
case 'payment':
|
|
if (action.isBulk) {
|
|
return _("Bulk Payment")
|
|
}
|
|
return _("Payment")
|
|
|
|
case 'transfer':
|
|
if (action.isBulk) {
|
|
return _("Bulk Transfer")
|
|
}
|
|
return _("Transfer")
|
|
|
|
case 'bank_entry':
|
|
if (action.isBulk) {
|
|
return _("Bulk Bank Entry")
|
|
}
|
|
return _("Bank Entry")
|
|
|
|
default:
|
|
return _("Action")
|
|
}
|
|
}, [action])
|
|
|
|
return <div className='flex items-center gap-2 text-ink-gray-5'>
|
|
{action.type === 'match' && <GitCompareIcon className='w-4 h-4' />}
|
|
{action.type === 'payment' && <ReceiptIcon className='w-4 h-4' />}
|
|
{action.type === 'transfer' && <ArrowRightLeftIcon className='w-4 h-4' />}
|
|
{action.type === 'bank_entry' && <LandmarkIcon className='w-4 h-4' />}
|
|
<span className='flex items-center gap-2 text-sm'>
|
|
{label} - {dayjs(action.timestamp).fromNow()}
|
|
</span>
|
|
</div>
|
|
}
|
|
|
|
const Row = ({ item, index, isLast, action }: { item: ActionLogItem, index: number, isLast: boolean, action: ActionLogType }) => {
|
|
|
|
const isWithdrawal = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0
|
|
|
|
const { banks } = useGetBankAccounts()
|
|
|
|
const bank = useMemo(() => {
|
|
if (item.bankTransaction.bank_account) {
|
|
return banks?.find((bank) => bank.name === item.bankTransaction.bank_account)
|
|
}
|
|
return null
|
|
}, [item.bankTransaction.bank_account, banks])
|
|
|
|
const amount = item.bankTransaction.withdrawal ? item.bankTransaction.withdrawal : item.bankTransaction.deposit
|
|
|
|
const currency = item.bankTransaction.currency || getCompanyCurrency(item.bankTransaction.company ?? '')
|
|
|
|
return <div className='flex items-center gap-2 group'>
|
|
<div className={cn('p-3.5 group-hover:bg-surface-gray-1 border-s border-e border-t w-full', isLast ? 'rounded-b border-b' : '', index === 0 ? 'rounded-t' : '')}>
|
|
<div className='flex justify-between items-center'>
|
|
<div className='flex flex-col gap-2'>
|
|
<p className='text-p-base'>{item.bankTransaction.description}</p>
|
|
<div className='flex items-center gap-3'>
|
|
<div className='flex gap-2 items-center'>
|
|
<BankLogo bank={bank} className='h-4 mb-0' iconSize='16px' />
|
|
<span className='text-sm text-ink-gray-5'>{item.bankTransaction.bank_account}</span>
|
|
</div>
|
|
<Separator orientation='vertical' />
|
|
<div className='flex items-center gap-2 text-ink-gray-5 text-sm' title={_("Transaction Date")}>
|
|
<CalendarIcon className='w-4 h-4' />
|
|
<span className='text-sm'>{formatDate(item.bankTransaction.date, 'Do MMM YYYY')}</span>
|
|
</div>
|
|
<Separator orientation='vertical' />
|
|
<div>
|
|
<div className='flex items-center gap-1' title={isWithdrawal ? _("Spent") : _("Received")}>
|
|
{isWithdrawal ? <ArrowUpRight className="w-5 h-5 text-ink-red-3" /> : <ArrowDownRight className="w-5 h-5 text-ink-green-3" />}
|
|
<span className='text-sm text-ink-gray-5'>{formatCurrency(amount, currency)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className='flex justify-end items-center gap-2'>
|
|
<div className='text-end flex flex-col gap-2'>
|
|
<a
|
|
href={`/desk/${slug(item.voucher.reference_doctype)}/${item.voucher.reference_name}`}
|
|
target='_blank'
|
|
className='underline underline-offset-4 text-base'>
|
|
{["Payment Entry", "Journal Entry"].includes(item.voucher.reference_doctype) ? "" : _("{} :", [item.voucher.reference_doctype])} {item.voucher.reference_name}
|
|
</a>
|
|
{item.voucher.reference_doctype === "Payment Entry" && item.voucher.doc && <PaymentEntryDetails item={item} />}
|
|
{item.voucher.reference_doctype === "Journal Entry" && <JournalEntryDetails item={item} bank={bank} />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className='w-10 h-10 flex items-center justify-center'>
|
|
<CancelActionLogItem item={item} type={action.type} timestamp={action.timestamp} bank={bank} />
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
const JournalEntryDetails = ({ item, bank }: { item: ActionLogItem, bank?: SelectedBank | null }) => {
|
|
|
|
return <div className='flex items-center gap-2 text-ink-gray-5 justify-end'>
|
|
<WalletIcon className='w-4 h-4' />
|
|
<JournalEntryAccountsTable item={item} bank={bank} />
|
|
</div>
|
|
}
|
|
|
|
const JournalEntryAccountsTable = ({ item, bank }: { item: ActionLogItem, bank?: SelectedBank | null }) => {
|
|
|
|
const accounts = useMemo(() => {
|
|
|
|
const allAccounts = (item.voucher.doc as JournalEntry).accounts
|
|
|
|
return allAccounts.filter((acc) => bank ? acc.account !== bank.account : true)
|
|
|
|
}, [item, bank])
|
|
|
|
return <>
|
|
{accounts.length === 1 ? <span className='text-sm'>{accounts[0].account}</span> :
|
|
<HoverCard>
|
|
<HoverCardTrigger>
|
|
<span className='text-sm cursor-pointer hover:underline underline-offset-4'>{_("Split across {} accounts", [accounts.length.toString()])}</span>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent className='w-full p-2' align='end'>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{_("Account")}</TableHead>
|
|
<TableHead className='text-end'>{_("Debit")}</TableHead>
|
|
<TableHead className='text-end'>{_("Credit")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{accounts.map((account) => (
|
|
<TableRow key={account.account}>
|
|
<TableCell>{account.account}</TableCell>
|
|
<TableCell className='text-end font-numeric'>{formatCurrency(account.debit ?? 0, account.account_currency ?? '')}</TableCell>
|
|
<TableCell className='text-end font-numeric'>{formatCurrency(account.credit ?? 0, account.account_currency ?? '')}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
}</>
|
|
}
|
|
|
|
const PaymentEntryDetails = ({ item, className }: { item: ActionLogItem, className?: string }) => {
|
|
if ((item.voucher.doc as PaymentEntry).payment_type === "Internal Transfer") {
|
|
return <TransferDetails item={item} className={className} />
|
|
}
|
|
|
|
const invoices = (item.voucher.doc as PaymentEntry).references ?? []
|
|
|
|
const currency = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0 ? (item.voucher.doc as PaymentEntry)?.paid_to_account_currency : (item.voucher.doc as PaymentEntry)?.paid_from_account_currency
|
|
|
|
return <div className='flex items-center gap-3'>
|
|
<div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
|
|
<UserIcon className='w-4 h-4' />
|
|
<span className='text-sm'>{(item.voucher.doc as PaymentEntry).party_name}</span>
|
|
</div>
|
|
<Separator orientation='vertical' />
|
|
<HoverCard>
|
|
<HoverCardTrigger>
|
|
<div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
|
|
<ReceiptTextIcon className='w-4 h-4' />
|
|
<span className='text-sm cursor-pointer hover:underline underline-offset-4'>{invoices.length === 0 ? _("No invoice linked") : invoices.length === 1 ? _("1 invoice") : _("{} invoices", [invoices.length.toString()])}</span>
|
|
</div>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent className='w-full p-2' align='end'>
|
|
<div className='flex flex-col gap-2'>
|
|
{invoices.map((invoice) => (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>{_("Document")}</TableHead>
|
|
<TableHead>{_("Invoice No")}</TableHead>
|
|
<TableHead>{_("Due Date")}</TableHead>
|
|
<TableHead className='text-end'>{_("Grand Total")}</TableHead>
|
|
<TableHead className='text-end'>{_("Allocated")}</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
<TableRow>
|
|
<TableCell><a href={`/desk/${slug(invoice.reference_doctype)}/${invoice.reference_name}`} target='_blank' className='underline underline-offset-4'>{invoice.reference_doctype}: {invoice.reference_name}</a></TableCell>
|
|
<TableCell>{invoice.bill_no ?? "-"}</TableCell>
|
|
<TableCell>{formatDate(invoice.due_date)}</TableCell>
|
|
<TableCell className='text-end font-numeric'>{formatCurrency(invoice.total_amount, currency ?? '')}</TableCell>
|
|
<TableCell className='text-end font-numeric'>{formatCurrency(invoice.allocated_amount, currency ?? '')}</TableCell>
|
|
</TableRow>
|
|
</TableBody>
|
|
</Table>
|
|
))}
|
|
</div>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
|
|
</div>
|
|
}
|
|
|
|
const TransferDetails = ({ item, className }: { item: ActionLogItem, className?: string }) => {
|
|
|
|
const { banks } = useGetBankAccounts()
|
|
|
|
const bank = useMemo(() => {
|
|
|
|
const isWithdrawal = item.bankTransaction.withdrawal && item.bankTransaction.withdrawal > 0
|
|
|
|
let transferAccount = ""
|
|
|
|
if (isWithdrawal) {
|
|
transferAccount = (item.voucher.doc as PaymentEntry).paid_to
|
|
} else {
|
|
transferAccount = (item.voucher.doc as PaymentEntry).paid_from
|
|
}
|
|
|
|
const transferBankAccount = banks?.find((bank) => bank.account === transferAccount)
|
|
|
|
return transferBankAccount
|
|
|
|
}, [banks, item])
|
|
|
|
return <div className={cn('flex items-center gap-2 text-ink-gray-5 text-sm', className)}>
|
|
<BankLogo bank={bank} className='h-5 mb-0' iconSize='16px' imageClassName='max-h-5' />
|
|
<span className='text-sm'>{bank?.account}</span>
|
|
</div>
|
|
}
|
|
|
|
const ACTION_TYPE_MAP = {
|
|
'bank_entry': _("Bank Entry"),
|
|
'payment': _("Payment"),
|
|
'transfer': _("Transfer"),
|
|
'match': _("Match"),
|
|
}
|
|
|
|
const CancelActionLogItem = ({ item, type, timestamp, bank }: { item: ActionLogItem, type: ActionLogType['type'], timestamp: number, bank?: SelectedBank | null }) => {
|
|
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
|
|
const { call, loading, error } = useFrappePostCall('erpnext.accounts.doctype.bank_transaction.bank_transaction.unreconcile_transaction_entry')
|
|
const { mutate } = useSWRConfig()
|
|
const actionLog = useSetAtom(bankRecActionLog)
|
|
const dates = useAtomValue(bankRecDateAtom)
|
|
const matchFilters = useAtomValue(bankRecMatchFilters)
|
|
const selectedBank = useAtomValue(selectedBankAccountAtom)
|
|
|
|
const onUndo = () => {
|
|
call({
|
|
bank_transaction_id: item.bankTransaction.name,
|
|
voucher_type: item.voucher.reference_doctype,
|
|
voucher_id: item.voucher.reference_name,
|
|
}).then(() => {
|
|
toast.success(type === 'match' ? _("Unmatched") : _("Cancelled"))
|
|
|
|
if (selectedBank?.name === item.bankTransaction.bank_account) {
|
|
mutate(`bank-reconciliation-unreconciled-transactions-${selectedBank?.name}-${dates.fromDate}-${dates.toDate}`)
|
|
mutate(`bank-reconciliation-account-closing-balance-${selectedBank?.name}-${dates.toDate}`)
|
|
// Update the matching vouchers for the selected transaction
|
|
mutate(`bank-reconciliation-vouchers-${item.bankTransaction.name}-${dates.fromDate}-${dates.toDate}-${matchFilters.join(',')}`)
|
|
}
|
|
|
|
setTimeout(() => {
|
|
actionLog((prev) => {
|
|
// Find the action and then remove the item from the action. If the action is empty, remove the action from the array
|
|
const action = prev.find((action) => action.timestamp === timestamp)
|
|
|
|
if (action) {
|
|
action.items = action.items.filter((i) => i.bankTransaction.name !== item.bankTransaction.name)
|
|
}
|
|
// If the action is empty, remove the action from the array
|
|
if (action && action.items.length === 0) {
|
|
return prev.filter((a) => a.timestamp !== timestamp)
|
|
} else {
|
|
return prev.map((a) => a.timestamp === timestamp ? { ...a, items: action?.items ?? [] } : a)
|
|
}
|
|
})
|
|
}, 100)
|
|
|
|
setIsOpen(false)
|
|
|
|
}).catch((error) => {
|
|
toast.error(_("There was an error while performing the action."), {
|
|
duration: 5000,
|
|
description: getErrorMessage(error),
|
|
})
|
|
})
|
|
}
|
|
|
|
return <AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<AlertDialogTrigger asChild>
|
|
<Button
|
|
variant={'ghost'}
|
|
isIconButton
|
|
theme='red'
|
|
title={_("Cancel")}
|
|
className='hover:text-ink-red-3 hover:bg-destructive/5 text-ink-gray-5 hidden group-hover:inline-flex'>
|
|
<CircleXIcon className='w-8 h-8' />
|
|
</Button>
|
|
</AlertDialogTrigger>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
{_("Cancel")}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<AlertDialogContent className='min-w-3xl'>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{type === 'match' ? _("Unmatch Transaction?") : _("Undo {}?", [item.voucher.reference_doctype])}</AlertDialogTitle>
|
|
<AlertDialogDescription>{type === 'match' ? _("Are you sure you want to unmatch the voucher from this transaction?") : _("Are you sure you want to cancel this {} {}?", [_(item.voucher.reference_doctype), item.voucher.reference_name])}</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
{error && <ErrorBanner error={error} />}
|
|
<div className='flex flex-col gap-2'>
|
|
<SelectedTransactionDetails transaction={item.bankTransaction} />
|
|
<Table>
|
|
<TableRow>
|
|
<TableHead>{_("Action Type")}</TableHead>
|
|
<TableCell>{ACTION_TYPE_MAP[type]}</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>{_("Voucher Type")}</TableHead>
|
|
<TableCell>{_(item.voucher.reference_doctype)}</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>{_("Voucher Name")}</TableHead>
|
|
<TableCell><a href={`/desk/${slug(item.voucher.reference_doctype)}/${item.voucher.reference_name}`} target='_blank' className='underline underline-offset-4'>{item.voucher.reference_name}</a></TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableHead>{_("Posting Date")}</TableHead>
|
|
<TableCell>{formatDate(item.voucher.posting_date, 'Do MMM YYYY')}</TableCell>
|
|
</TableRow>
|
|
{type === 'transfer' && item.voucher.doc && <TableRow>
|
|
<TableHead>{_("Transfer Account")}</TableHead>
|
|
<TableCell>
|
|
<TransferDetails item={item} className='text-ink-gray-8' />
|
|
</TableCell>
|
|
</TableRow>}
|
|
{type === 'payment' && item.voucher.doc && <TableRow>
|
|
<TableHead>{_("Payment Details")}</TableHead>
|
|
<TableCell>
|
|
<PaymentEntryDetails item={item} className='text-ink-gray-8' />
|
|
</TableCell>
|
|
</TableRow>}
|
|
{type === 'bank_entry' && item.voucher.doc && <TableRow>
|
|
<TableHead>{_("Account")}</TableHead>
|
|
<TableCell><JournalEntryAccountsTable item={item} bank={bank} /></TableCell>
|
|
</TableRow>}
|
|
</Table>
|
|
</div>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel disabled={loading}>
|
|
{_("Close")}
|
|
</AlertDialogCancel>
|
|
<Button theme="red" size='md' disabled={loading} onClick={onUndo}>
|
|
{loading ? <Loader2Icon className='w-4 h-4 animate-spin' /> : _(("Undo"))}
|
|
</Button>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
}
|
|
|
|
export default ActionLog |