mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-14 10:41:21 +00:00
fix: UI/UX issues in new banking module (#54824)
* fix: enforce user permissions on bank account get_list * feat: auto-select last used bank account * fix: skeleton loaders in bank balance * fix: show empty state for no bank transactions * chore: add Stripe and PayPal logos * fix: alignment of header text in list-view * fix: wrap words in transaction description * fix: change file-dropzone color on hover
This commit is contained in:
@@ -53,7 +53,7 @@ const OpeningBalance = () => {
|
||||
|
||||
return <StatContainer className="min-w-48">
|
||||
<StatLabel>{_("Opening Balance")}</StatLabel>
|
||||
{isLoading ? <Skeleton className="w-[150px] h-9" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
</StatContainer>
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ const ClosingBalance = () => {
|
||||
</HoverCard>
|
||||
|
||||
</div>
|
||||
{isLoading ? <Skeleton className="w-[150px] h-9" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
</StatContainer>
|
||||
)
|
||||
}
|
||||
@@ -104,7 +104,7 @@ const Difference = () => {
|
||||
|
||||
return <StatContainer className="w-fit text-end sm:min-w-56">
|
||||
<StatLabel className="text-end">{_("Difference")}</StatLabel>
|
||||
{isLoading ? <Skeleton className="w-[150px] h-9" /> : <StatValue className={isError ? 'text-ink-red-3 font-numeric' : 'font-numeric'}>
|
||||
{isLoading ? <Skeleton className="w-[150px] h-5 self-end rounded-sm" /> : <StatValue className={isError ? 'text-ink-red-3 font-numeric' : 'font-numeric'}>
|
||||
{formatCurrency(difference,
|
||||
bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))
|
||||
}</StatValue>}
|
||||
@@ -175,7 +175,7 @@ const ClosingBalanceAsPerStatement = () => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex items-center gap-4 underline cursor-pointer underline-offset-6" role="button">
|
||||
{isLoading ? <Skeleton className="w-[150px] h-9" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message?.balance, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
{isLoading ? <Skeleton className="w-[150px] h-5 rounded-sm" /> : <StatValue className="font-numeric">{formatCurrency(flt(data?.message?.balance, 2), bankAccount?.account_currency ?? getCompanyCurrency(bankAccount?.company ?? ''))}</StatValue>}
|
||||
<Edit className="w-4 h-4" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAtom, useSetAtom } from "jotai"
|
||||
import { useAtom } from "jotai"
|
||||
import { SelectedBank, selectedBankAccountAtom } from "./bankRecAtoms"
|
||||
import { useCallback } from "react"
|
||||
import { useGetBankAccounts, useGetUnreconciledTransactions } from "./utils"
|
||||
@@ -16,9 +16,16 @@ import { useCurrentCompany } from "@/hooks/useCurrentCompany"
|
||||
|
||||
const BankPicker = ({ className }: { className?: string }) => {
|
||||
|
||||
const setSelectedBank = useSetAtom(selectedBankAccountAtom)
|
||||
const [selectedBank, setSelectedBank] = useAtom(selectedBankAccountAtom)
|
||||
|
||||
const onLoadingSuccess = useCallback((data?: SelectedBank[]) => {
|
||||
// If the bank is already selected, then don't set it again
|
||||
if (selectedBank) {
|
||||
// Check if selected bank is in the data
|
||||
if (data?.some((bank: SelectedBank) => bank.name === selectedBank.name)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!data) return
|
||||
if (data.length === 1) {
|
||||
setSelectedBank(data[0])
|
||||
@@ -26,9 +33,12 @@ const BankPicker = ({ className }: { className?: string }) => {
|
||||
const defaultBank = data.find((bank: SelectedBank) => bank.is_default)
|
||||
if (defaultBank) {
|
||||
setSelectedBank(defaultBank)
|
||||
} else {
|
||||
// Select the first available bank account
|
||||
setSelectedBank(data[0])
|
||||
}
|
||||
}
|
||||
}, [setSelectedBank])
|
||||
}, [setSelectedBank, selectedBank])
|
||||
|
||||
const selectedCompany = useCurrentCompany()
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useDebounceValue } from "usehooks-ts"
|
||||
import type { ColumnDef } from "@tanstack/react-table"
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { Link } from "react-router"
|
||||
import { Empty, EmptyTitle, EmptyHeader, EmptyMedia, EmptyDescription } from "@/components/ui/empty"
|
||||
import { Empty, EmptyTitle, EmptyHeader, EmptyMedia, EmptyDescription, EmptyContent } from "@/components/ui/empty"
|
||||
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"
|
||||
|
||||
const BankTransactions = () => {
|
||||
@@ -262,7 +262,7 @@ const BankTransactionListView = () => {
|
||||
|
||||
{error && <ErrorBanner error={error} />}
|
||||
|
||||
{data && data.message.length > 0 && <Filters
|
||||
<Filters
|
||||
onSearchChange={onSearchChange}
|
||||
search={search}
|
||||
results={filteredResults}
|
||||
@@ -272,28 +272,31 @@ const BankTransactionListView = () => {
|
||||
typeFilter={typeFilter}
|
||||
status={status}
|
||||
setStatus={setStatus}
|
||||
/>}
|
||||
|
||||
{data && data.message.length > 0 ? (
|
||||
<ListView
|
||||
data={filteredResults}
|
||||
columns={transactionColumns}
|
||||
getRowId={(row) => row.name}
|
||||
maxHeight="calc(100vh - 200px)"
|
||||
scrollAreaClassName="min-h-[calc(100vh-200px)]"
|
||||
emptyState={<Empty>
|
||||
<EmptyMedia>
|
||||
<ListIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{_("No bank transactions found")}</EmptyTitle>
|
||||
<EmptyDescription>{_("There are no transactions in the system for the selected bank account and dates that match the filters.")}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
</Empty>}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
/>
|
||||
|
||||
<ListView
|
||||
data={filteredResults}
|
||||
columns={transactionColumns}
|
||||
getRowId={(row) => row.name}
|
||||
maxHeight="calc(100vh - 200px)"
|
||||
scrollAreaClassName="min-h-[calc(100vh-200px)]"
|
||||
emptyState={<Empty>
|
||||
<EmptyMedia>
|
||||
<ListIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{_("No bank transactions found")}</EmptyTitle>
|
||||
<EmptyDescription>{_("There are no transactions in the system for the selected bank account and dates that match the filters.")}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
{data && data.message.length === 0 ? <EmptyContent>
|
||||
<Button type='button' asChild variant='outline'>
|
||||
<Link to="/statement-importer">
|
||||
{_("Import Bank Statement")}
|
||||
</Link>
|
||||
</Button>
|
||||
</EmptyContent> : null}
|
||||
</Empty>}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ const UnreconciledTransactionItem = ({ transaction }: { transaction: Unreconcile
|
||||
tabIndex={0}
|
||||
onClick={handleSelectTransaction}>
|
||||
<div className="flex justify-between items-start w-full">
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-1 overflow-hidden whitespace-pre-wrap">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="font-medium text-sm">{formatDate(transaction.date)}</span>
|
||||
{transaction.transaction_type &&
|
||||
@@ -314,7 +314,7 @@ const UnreconciledTransactionItem = ({ transaction }: { transaction: Unreconcile
|
||||
title={_("Matched by rule")}>
|
||||
<ZapIcon className="w-4 h-4" /> {transaction.matched_transaction_rule}</Badge>}
|
||||
</div>
|
||||
<span className="text-sm">{transaction.description}</span>
|
||||
<span className="text-sm wrap-anywhere" title={transaction.description}>{transaction.description}</span>
|
||||
</div>
|
||||
<div className="gap-1 flex flex-col items-end min-w-36 h-full text-end">
|
||||
{isWithdrawal ? <ArrowUpRight className="size-5 text-ink-red-3" /> : <ArrowDownRight className="size-5 text-ink-green-3" />}
|
||||
|
||||
@@ -15,7 +15,9 @@ export interface SelectedBank extends Pick<BankAccount, 'name' | 'bank' | 'is_cr
|
||||
logoClassName?: string,
|
||||
account_currency?: string
|
||||
}
|
||||
export const selectedBankAccountAtom = atom<SelectedBank | null>(null)
|
||||
export const selectedBankAccountAtom = atomWithStorage<SelectedBank | null>('bank-rec-selected-bank', null, undefined, {
|
||||
getOnInit: true
|
||||
})
|
||||
|
||||
export const bankRecDateAtom = atomWithStorage<{ fromDate: string, toDate: string }>("bank-rec-date", {
|
||||
fromDate: getDatesForTimePeriod('This Month').fromDate,
|
||||
|
||||
@@ -393,5 +393,18 @@ export const BANK_LOGOS: { keywords: string[], logo: string, locale?: string[],
|
||||
logo: "Prime_Bank.png",
|
||||
locale: ['Kenya'],
|
||||
logoClassName: 'max-w-28'
|
||||
},
|
||||
{
|
||||
keywords: ["Stripe"],
|
||||
logo: "Stripe.svg",
|
||||
locale: ['Global'],
|
||||
logoClassName: 'h-6',
|
||||
darkModeInvert: true,
|
||||
},
|
||||
{
|
||||
keywords: ["PayPal"],
|
||||
logo: "PayPal.png",
|
||||
locale: ['Global'],
|
||||
logoClassName: 'h-6',
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user