feat: Implement new General Ledger frontend section with comprehensive report screens and cash/bank account management

- Added a new Ledgers sidebar section for statutory-format financial reports and cash/bank-account management.
- Introduced dedicated GL client for API interactions, handling response envelopes and error management.
- Developed report screens for Trial Balance, Balance Sheet, General Ledger, Profit & Loss, Cash Flow, Budget vs Actual, and a new Tax Report.
- Implemented CSV download functionality alongside existing PDF downloads for all report screens.
- Separated Cash and Bank accounts into distinct tables/endpoints, with updated create forms and unified list view.
- Created a new Accounts section for Cheque Management, moving Cash/Bank Accounts from the Ledgers section.
- Updated RBAC navigation to include new permissions and sub-navigation items for the added features.
- Ensured compliance with GL's updated API contract, including renaming fields and adjusting response shapes.
- Addressed various bugs and presentation issues, enhancing user experience across the new module.
This commit is contained in:
2026-07-31 17:57:55 +05:30
parent 76484c7268
commit 22657f0910
49 changed files with 5707 additions and 27 deletions
@@ -0,0 +1,381 @@
"use client"
import { useState } from "react"
import { chequePagesApi } from "@/lib/api/general-ledger"
import { errorMessage } from "@/lib/error-map"
import { formatAmount, formatReportDate } from "@/lib/format"
import { validateIssueChequeForm } from "@/lib/validations/general-ledger"
import { cn } from "@/lib/utils"
import { ChequePage, ChequePageIssueStatus, ChequePageStatusAction, PayeeType } from "@/types/general-ledger"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { toast } from "@/components/ui/toast"
const STATUS_BADGE: Record<ChequePageIssueStatus, string> = {
[ChequePageIssueStatus.Unused]: "bg-muted text-muted-foreground",
[ChequePageIssueStatus.Issued]: "bg-primary/10 text-primary",
[ChequePageIssueStatus.Cleared]: "bg-success/10 text-success",
[ChequePageIssueStatus.Bounced]: "bg-destructive/10 text-destructive",
[ChequePageIssueStatus.Cancelled]: "bg-destructive/10 text-destructive",
[ChequePageIssueStatus.Void]: "bg-muted text-muted-foreground",
}
type PendingAction = "Issue" | ChequePageStatusAction | null
interface ChequePageDialogProps {
page: ChequePage | null
open: boolean
onOpenChange: (open: boolean) => void
/** Called with the server's response after a successful issue/status-update, so the caller's list stays in sync. */
onUpdated: (updated: ChequePage) => void
}
/** View a single cheque page's details, and (from `Unused`/`Issued`) issue it or move it through
* Clear/Bounce/Cancel/Void — a modal rather than a separate page, so acting on several leaves from
* a book's page list doesn't lose scroll position/context each time (docs/21 §7). */
export function ChequePageDialog({ page, open, onOpenChange, onUpdated }: ChequePageDialogProps) {
const [pendingAction, setPendingAction] = useState<PendingAction>(null)
const [submitting, setSubmitting] = useState(false)
const [errors, setErrors] = useState<Record<string, string>>({})
const [payeeType, setPayeeType] = useState<PayeeType>(PayeeType.Supplier)
const [payeeName, setPayeeName] = useState("")
const [payeeId, setPayeeId] = useState("")
const [issueDate, setIssueDate] = useState("")
const [amount, setAmount] = useState("")
const [currencyCode, setCurrencyCode] = useState("LKR")
const [voucherId, setVoucherId] = useState("")
const [referenceNo, setReferenceNo] = useState("")
const [purpose, setPurpose] = useState("")
const [isCrossCheque, setIsCrossCheque] = useState(false)
const [isAccountPayee, setIsAccountPayee] = useState(false)
const [isPostDated, setIsPostDated] = useState(false)
const [notes, setNotes] = useState("")
const [printedBy, setPrintedBy] = useState("")
const [clearedDate, setClearedDate] = useState("")
const [cancelReason, setCancelReason] = useState("")
const [performedBy, setPerformedBy] = useState("")
function resetActionState() {
setPendingAction(null)
setErrors({})
setPayeeType(PayeeType.Supplier)
setPayeeName("")
setPayeeId("")
setIssueDate("")
setAmount("")
setCurrencyCode("LKR")
setVoucherId("")
setReferenceNo("")
setPurpose("")
setIsCrossCheque(false)
setIsAccountPayee(false)
setIsPostDated(false)
setNotes("")
setPrintedBy("")
setClearedDate("")
setCancelReason("")
setPerformedBy("")
}
function handleOpenChange(next: boolean) {
if (!next) resetActionState()
onOpenChange(next)
}
async function submitIssue() {
if (!page) return
const nextErrors = validateIssueChequeForm({ payeeName, issueDate, amount })
setErrors(nextErrors)
if (Object.keys(nextErrors).length > 0) return
setSubmitting(true)
try {
const updated = await chequePagesApi.issue(page.chequeNo, {
payeeType,
payeeId: payeeId ? Number(payeeId) : undefined,
payeeName,
issueDate,
amount: Number(amount),
currencyCode: currencyCode || undefined,
voucherId: voucherId ? Number(voucherId) : undefined,
referenceNo: referenceNo || undefined,
purpose: purpose || undefined,
isCrossCheque,
isAccountPayee,
isPostDated,
notes: notes || undefined,
printedBy: printedBy || undefined,
})
toast.success("Cheque issued", `${updated.chequeNo}${updated.payeeName}`)
onUpdated(updated)
resetActionState()
} catch (err) {
toast.error("Could not issue cheque", errorMessage(err))
} finally {
setSubmitting(false)
}
}
async function submitStatusAction(action: ChequePageStatusAction) {
if (!page) return
if (action === ChequePageStatusAction.Clear && !clearedDate) {
setErrors({ clearedDate: "Cleared date is required" })
return
}
if (action === ChequePageStatusAction.Cancel && !cancelReason.trim()) {
setErrors({ cancelReason: "Cancel reason is required" })
return
}
setSubmitting(true)
try {
const updated = await chequePagesApi.updateStatus(page.chequeNo, {
action,
clearedDate: action === ChequePageStatusAction.Clear ? clearedDate : undefined,
cancelReason: action === ChequePageStatusAction.Cancel ? cancelReason : undefined,
performedBy: performedBy || undefined,
})
toast.success(`Cheque ${action.toLowerCase()}d`, updated.chequeNo)
onUpdated(updated)
resetActionState()
} catch (err) {
toast.error(`Could not ${action.toLowerCase()} cheque`, errorMessage(err))
} finally {
setSubmitting(false)
}
}
if (!page) return null
const availableActions: ChequePageStatusAction[] =
page.issueStatus === ChequePageIssueStatus.Unused
? [ChequePageStatusAction.Cancel, ChequePageStatusAction.Void]
: page.issueStatus === ChequePageIssueStatus.Issued
? [ChequePageStatusAction.Clear, ChequePageStatusAction.Bounce, ChequePageStatusAction.Cancel]
: []
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-lg">
Cheque {page.chequeNo}
<Badge variant="outline" className={cn("h-6 justify-center border-transparent text-sm", STATUS_BADGE[page.issueStatus])}>
{page.issueStatus}
</Badge>
</DialogTitle>
</DialogHeader>
{!pendingAction && (
<div className="flex flex-col gap-4">
<dl className="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
<dt className="text-muted-foreground">Payee</dt>
<dd>{page.payeeName ?? "—"}</dd>
<dt className="text-muted-foreground">Payee type</dt>
<dd>{page.payeeType ?? "—"}</dd>
<dt className="text-muted-foreground">Issue date</dt>
<dd>{formatReportDate(page.issueDate)}</dd>
<dt className="text-muted-foreground">Amount</dt>
<dd>{page.amount !== null ? formatAmount(page.amount) : "—"}</dd>
<dt className="text-muted-foreground">Reference no.</dt>
<dd>{page.referenceNo ?? "—"}</dd>
<dt className="text-muted-foreground">Purpose</dt>
<dd>{page.purpose ?? "—"}</dd>
<dt className="text-muted-foreground">Notes</dt>
<dd>{page.notes ?? "—"}</dd>
{page.issueStatus === ChequePageIssueStatus.Cleared && (
<>
<dt className="text-muted-foreground">Cleared date</dt>
<dd>{formatReportDate(page.clearedDate)}</dd>
</>
)}
{page.issueStatus === ChequePageIssueStatus.Cancelled && (
<>
<dt className="text-muted-foreground">Cancel reason</dt>
<dd>{page.cancelReason ?? "—"}</dd>
</>
)}
</dl>
{availableActions.length > 0 && (
<div className="flex flex-wrap gap-2 border-t pt-4">
{page.issueStatus === ChequePageIssueStatus.Unused && (
<Button size="sm" onClick={() => setPendingAction("Issue")}>
Issue Cheque
</Button>
)}
{availableActions.map((action) => (
<Button key={action} size="sm" variant="outline" onClick={() => setPendingAction(action)}>
{action}
</Button>
))}
</div>
)}
</div>
)}
{pendingAction === "Issue" && (
<FieldGroup>
<Field data-invalid={!!errors.payeeName}>
<FieldLabel htmlFor="cp-payee-name">Payee name</FieldLabel>
<Input id="cp-payee-name" value={payeeName} onChange={(e) => setPayeeName(e.target.value)} aria-invalid={!!errors.payeeName} />
<FieldError errors={[errors.payeeName ? { message: errors.payeeName } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="cp-payee-type">Payee type</FieldLabel>
<Select<PayeeType> value={payeeType} onValueChange={(v) => setPayeeType(v ?? PayeeType.Supplier)}>
<SelectTrigger id="cp-payee-type" className="w-full text-base">
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.values(PayeeType).map((t) => (
<SelectItem key={t} value={t} label={t} className="text-base">
{t}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field>
<FieldLabel htmlFor="cp-payee-id">Payee ID (optional)</FieldLabel>
<Input id="cp-payee-id" type="number" value={payeeId} onChange={(e) => setPayeeId(e.target.value)} />
</Field>
<div className="grid grid-cols-2 gap-3">
<Field data-invalid={!!errors.issueDate}>
<FieldLabel htmlFor="cp-issue-date">Issue date</FieldLabel>
<Input
id="cp-issue-date"
type="date"
value={issueDate}
onChange={(e) => setIssueDate(e.target.value)}
aria-invalid={!!errors.issueDate}
/>
<FieldError errors={[errors.issueDate ? { message: errors.issueDate } : undefined]} />
</Field>
<Field data-invalid={!!errors.amount}>
<FieldLabel htmlFor="cp-amount">Amount</FieldLabel>
<Input id="cp-amount" type="number" value={amount} onChange={(e) => setAmount(e.target.value)} aria-invalid={!!errors.amount} />
<FieldError errors={[errors.amount ? { message: errors.amount } : undefined]} />
</Field>
</div>
<div className="grid grid-cols-2 gap-3">
<Field>
<FieldLabel htmlFor="cp-currency">Currency</FieldLabel>
<Input id="cp-currency" value={currencyCode} onChange={(e) => setCurrencyCode(e.target.value)} maxLength={3} />
</Field>
<Field>
<FieldLabel htmlFor="cp-voucher">Voucher ID (optional)</FieldLabel>
<Input id="cp-voucher" type="number" value={voucherId} onChange={(e) => setVoucherId(e.target.value)} />
</Field>
</div>
<Field>
<FieldLabel htmlFor="cp-ref">Reference no. (optional)</FieldLabel>
<Input id="cp-ref" value={referenceNo} onChange={(e) => setReferenceNo(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="cp-purpose">Purpose (optional)</FieldLabel>
<Input id="cp-purpose" value={purpose} onChange={(e) => setPurpose(e.target.value)} />
</Field>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2 text-sm">
<Checkbox checked={isCrossCheque} onCheckedChange={(v) => setIsCrossCheque(v === true)} />
Cross cheque
</label>
<label className="flex items-center gap-2 text-sm">
<Checkbox checked={isAccountPayee} onCheckedChange={(v) => setIsAccountPayee(v === true)} />
Account payee
</label>
<label className="flex items-center gap-2 text-sm">
<Checkbox checked={isPostDated} onCheckedChange={(v) => setIsPostDated(v === true)} />
Post-dated
</label>
</div>
<Field>
<FieldLabel htmlFor="cp-notes">Notes (optional)</FieldLabel>
<Input id="cp-notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="cp-printed-by">Printed by (optional)</FieldLabel>
<Input id="cp-printed-by" value={printedBy} onChange={(e) => setPrintedBy(e.target.value)} />
</Field>
</FieldGroup>
)}
{pendingAction === ChequePageStatusAction.Clear && (
<FieldGroup>
<Field data-invalid={!!errors.clearedDate}>
<FieldLabel htmlFor="cp-cleared-date">Cleared date</FieldLabel>
<Input
id="cp-cleared-date"
type="date"
value={clearedDate}
onChange={(e) => setClearedDate(e.target.value)}
aria-invalid={!!errors.clearedDate}
/>
<FieldError errors={[errors.clearedDate ? { message: errors.clearedDate } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="cp-performed-by">Performed by (optional)</FieldLabel>
<Input id="cp-performed-by" value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
</Field>
</FieldGroup>
)}
{pendingAction === ChequePageStatusAction.Cancel && (
<FieldGroup>
<Field data-invalid={!!errors.cancelReason}>
<FieldLabel htmlFor="cp-cancel-reason">Cancel reason</FieldLabel>
<Input
id="cp-cancel-reason"
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
aria-invalid={!!errors.cancelReason}
/>
<FieldError errors={[errors.cancelReason ? { message: errors.cancelReason } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="cp-performed-by">Performed by (optional)</FieldLabel>
<Input id="cp-performed-by" value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
</Field>
</FieldGroup>
)}
{(pendingAction === ChequePageStatusAction.Bounce || pendingAction === ChequePageStatusAction.Void) && (
<FieldGroup>
<Field>
<FieldLabel htmlFor="cp-performed-by">Performed by (optional)</FieldLabel>
<Input id="cp-performed-by" value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
</Field>
</FieldGroup>
)}
{pendingAction && (
<DialogFooter>
<Button variant="outline" onClick={() => resetActionState()} disabled={submitting}>
Back
</Button>
<Button onClick={() => (pendingAction === "Issue" ? submitIssue() : submitStatusAction(pendingAction))} disabled={submitting}>
{submitting ? "Submitting…" : pendingAction === "Issue" ? "Issue Cheque" : pendingAction}
</Button>
</DialogFooter>
)}
</DialogContent>
</Dialog>
)
}
@@ -0,0 +1,236 @@
"use client"
import { useEffect, useState } from "react"
import { bankAccountsApi, receivedChequesApi } from "@/lib/api/general-ledger"
import { errorMessage } from "@/lib/error-map"
import { formatAmount, formatReportDate } from "@/lib/format"
import { cn } from "@/lib/utils"
import {
CashAndBankAccountDto,
CashBankAccountType,
ReceivedCheque,
ReceivedChequeStatus,
ReceivedChequeStatusAction,
} from "@/types/general-ledger"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { toast } from "@/components/ui/toast"
const STATUS_BADGE: Record<ReceivedChequeStatus, string> = {
[ReceivedChequeStatus.Received]: "bg-muted text-muted-foreground",
[ReceivedChequeStatus.Deposited]: "bg-primary/10 text-primary",
[ReceivedChequeStatus.Cleared]: "bg-success/10 text-success",
[ReceivedChequeStatus.Returned]: "bg-destructive/10 text-destructive",
[ReceivedChequeStatus.Cancelled]: "bg-destructive/10 text-destructive",
}
interface ReceivedChequeDialogProps {
cheque: ReceivedCheque | null
open: boolean
onOpenChange: (open: boolean) => void
onUpdated: (updated: ReceivedCheque) => void
}
/** View a received cheque's details and (from `Received`/`Deposited`) move it through
* Deposit/Clear/Return/Cancel — a modal, same posture as `ChequePageDialog`. */
export function ReceivedChequeDialog({ cheque, open, onOpenChange, onUpdated }: ReceivedChequeDialogProps) {
const [pendingAction, setPendingAction] = useState<ReceivedChequeStatusAction | null>(null)
const [submitting, setSubmitting] = useState(false)
const [errors, setErrors] = useState<Record<string, string>>({})
const [bankAccounts, setBankAccounts] = useState<CashAndBankAccountDto[] | null>(null)
const [depositBankAccountId, setDepositBankAccountId] = useState("")
const [depositDate, setDepositDate] = useState("")
const [performedBy, setPerformedBy] = useState("")
const [notes, setNotes] = useState("")
useEffect(() => {
if (pendingAction !== ReceivedChequeStatusAction.Deposit || bankAccounts !== null) return
bankAccountsApi.list(CashBankAccountType.Bank).then(setBankAccounts).catch(() => setBankAccounts([]))
// Only fetched once, lazily, the first time Deposit is chosen.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pendingAction])
function resetActionState() {
setPendingAction(null)
setErrors({})
setDepositBankAccountId("")
setDepositDate("")
setPerformedBy("")
setNotes("")
}
function handleOpenChange(next: boolean) {
if (!next) resetActionState()
onOpenChange(next)
}
async function submitStatusAction(action: ReceivedChequeStatusAction) {
if (!cheque) return
if (action === ReceivedChequeStatusAction.Deposit) {
const nextErrors: Record<string, string> = {}
if (!depositBankAccountId) nextErrors.depositBankAccountId = "Select a deposit bank account"
if (!depositDate) nextErrors.depositDate = "Deposit date is required"
setErrors(nextErrors)
if (Object.keys(nextErrors).length > 0) return
}
setSubmitting(true)
try {
const updated = await receivedChequesApi.updateStatus(cheque.receivedChequeId, {
action,
depositBankAccountId: action === ReceivedChequeStatusAction.Deposit ? Number(depositBankAccountId) : undefined,
depositDate: action === ReceivedChequeStatusAction.Deposit ? depositDate : undefined,
notes: notes || undefined,
performedBy: performedBy || undefined,
})
toast.success(`Cheque ${action.toLowerCase()}ed`, updated.chequeNo)
onUpdated(updated)
resetActionState()
} catch (err) {
toast.error(`Could not ${action.toLowerCase()} cheque`, errorMessage(err))
} finally {
setSubmitting(false)
}
}
if (!cheque) return null
const availableActions: ReceivedChequeStatusAction[] =
cheque.status === ReceivedChequeStatus.Received
? [ReceivedChequeStatusAction.Deposit, ReceivedChequeStatusAction.Cancel]
: cheque.status === ReceivedChequeStatus.Deposited
? [ReceivedChequeStatusAction.Clear, ReceivedChequeStatusAction.Return]
: []
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-lg">
Cheque {cheque.chequeNo}
<Badge variant="outline" className={cn("h-6 justify-center border-transparent text-sm", STATUS_BADGE[cheque.status])}>
{cheque.status}
</Badge>
</DialogTitle>
</DialogHeader>
{!pendingAction && (
<div className="flex flex-col gap-4">
<dl className="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
<dt className="text-muted-foreground">Received from</dt>
<dd>{cheque.receivedFromName}</dd>
<dt className="text-muted-foreground">Type</dt>
<dd>{cheque.receivedFromType}</dd>
<dt className="text-muted-foreground">Cheque date</dt>
<dd>{formatReportDate(cheque.chequeDate)}</dd>
<dt className="text-muted-foreground">Amount</dt>
<dd>{formatAmount(cheque.amount)}</dd>
<dt className="text-muted-foreground">Received date</dt>
<dd>{formatReportDate(cheque.receivedDate)}</dd>
<dt className="text-muted-foreground">Drawer bank</dt>
<dd>{cheque.drawerBankName ?? "—"}</dd>
<dt className="text-muted-foreground">Drawer branch</dt>
<dd>{cheque.drawerBankBranch ?? "—"}</dd>
<dt className="text-muted-foreground">Account holder</dt>
<dd>{cheque.accountHolderName ?? "—"}</dd>
<dt className="text-muted-foreground">Reference</dt>
<dd>{cheque.referenceType ?? "—"}</dd>
<dt className="text-muted-foreground">Notes</dt>
<dd>{cheque.notes ?? "—"}</dd>
{cheque.status === ReceivedChequeStatus.Deposited && (
<>
<dt className="text-muted-foreground">Deposited to</dt>
<dd>#{cheque.depositBankAccountId}</dd>
<dt className="text-muted-foreground">Deposit date</dt>
<dd>{formatReportDate(cheque.depositDate)}</dd>
</>
)}
</dl>
{availableActions.length > 0 && (
<div className="flex flex-wrap gap-2 border-t pt-4">
{availableActions.map((action) => (
<Button key={action} size="sm" variant="outline" onClick={() => setPendingAction(action)}>
{action}
</Button>
))}
</div>
)}
</div>
)}
{pendingAction === ReceivedChequeStatusAction.Deposit && (
<FieldGroup>
<Field data-invalid={!!errors.depositBankAccountId}>
<FieldLabel htmlFor="rc-deposit-bank">Deposit bank account</FieldLabel>
<Select<string> value={depositBankAccountId} onValueChange={(v) => setDepositBankAccountId(v ?? "")}>
<SelectTrigger id="rc-deposit-bank" className="w-full text-base" aria-invalid={!!errors.depositBankAccountId}>
<SelectValue placeholder={bankAccounts === null ? "Loading…" : "Select a bank account"} />
</SelectTrigger>
<SelectContent>
{(bankAccounts ?? []).map((a) => (
<SelectItem key={a.accountId} value={String(a.accountId)} label={a.accountName} className="text-base">
{a.accountName}
</SelectItem>
))}
</SelectContent>
</Select>
<FieldError errors={[errors.depositBankAccountId ? { message: errors.depositBankAccountId } : undefined]} />
</Field>
<Field data-invalid={!!errors.depositDate}>
<FieldLabel htmlFor="rc-deposit-date">Deposit date</FieldLabel>
<Input
id="rc-deposit-date"
type="date"
value={depositDate}
onChange={(e) => setDepositDate(e.target.value)}
aria-invalid={!!errors.depositDate}
/>
<FieldError errors={[errors.depositDate ? { message: errors.depositDate } : undefined]} />
</Field>
<Field>
<FieldLabel htmlFor="rc-performed-by">Performed by (optional)</FieldLabel>
<Input id="rc-performed-by" value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-notes">Notes (optional)</FieldLabel>
<Input id="rc-notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</Field>
</FieldGroup>
)}
{(pendingAction === ReceivedChequeStatusAction.Clear ||
pendingAction === ReceivedChequeStatusAction.Return ||
pendingAction === ReceivedChequeStatusAction.Cancel) && (
<FieldGroup>
<Field>
<FieldLabel htmlFor="rc-performed-by">Performed by (optional)</FieldLabel>
<Input id="rc-performed-by" value={performedBy} onChange={(e) => setPerformedBy(e.target.value)} />
</Field>
<Field>
<FieldLabel htmlFor="rc-notes">Notes (optional)</FieldLabel>
<Input id="rc-notes" value={notes} onChange={(e) => setNotes(e.target.value)} />
</Field>
</FieldGroup>
)}
{pendingAction && (
<DialogFooter>
<Button variant="outline" onClick={() => resetActionState()} disabled={submitting}>
Back
</Button>
<Button onClick={() => submitStatusAction(pendingAction)} disabled={submitting}>
{submitting ? "Submitting…" : pendingAction}
</Button>
</DialogFooter>
)}
</DialogContent>
</Dialog>
)
}