Transaction dialogs: allow all account types as the category

Banking deposit/payment + bulk-categorize and the reconcile add-transaction
dialog previously limited the category to income (deposits) or expense
(payments). Now every account type is selectable, grouped by Income /
Expense / Assets / Liabilities / Equity (with codes); the direction's
natural type is listed first.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 23:18:56 -04:00
parent 3f0c7ba1bb
commit ad74072061
2 changed files with 43 additions and 32 deletions
+26 -23
View File
@@ -156,6 +156,17 @@ export default function AccountingBankingPage() {
const bankAccounts = useMemo(() => (accounts as any[]).filter((a) => a.is_bank), [accounts]);
const incomeAccounts = useMemo(() => (accounts as any[]).filter((a) => a.type === "income"), [accounts]);
const expenseAccounts = useMemo(() => (accounts as any[]).filter((a) => a.type === "expense"), [accounts]);
// All account types are selectable as the offsetting account/category — the
// natural type for the direction (income for deposits, expense for payments)
// is listed first, then the rest (assets, liabilities, equity).
const coaGroupsFor = (type: "credit" | "debit") => {
const g = (t: string) => (accounts as any[]).filter((a) => a.type === t);
const order = type === "credit"
? ["income", "expense", "asset", "liability", "equity"]
: ["expense", "income", "asset", "liability", "equity"];
const label: Record<string, string> = { income: "Income", expense: "Expense", asset: "Assets", liability: "Liabilities", equity: "Equity" };
return order.map((t) => ({ label: label[t], items: g(t) })).filter((grp) => grp.items.length > 0);
};
const { data: vendors = [] } = useQuery({
queryKey: ["vendors-lookup", cid],
@@ -541,7 +552,6 @@ export default function AccountingBankingPage() {
});
};
const coaOptions = txForm.type === "credit" ? incomeAccounts : expenseAccounts;
const saveTransfer = async () => {
const { from_account_id, to_account_id, date, memo } = transfer;
@@ -797,22 +807,14 @@ export default function AccountingBankingPage() {
<Select value="" onValueChange={bulkSetCategory}>
<SelectTrigger className="h-8 w-56 text-sm"><SelectValue placeholder="Set category…" /></SelectTrigger>
<SelectContent>
{incomeAccounts.length > 0 && (
<SelectGroup>
<SelectLabel>Income</SelectLabel>
{incomeAccounts.map((a: any) => (
{coaGroupsFor("debit").map((grp) => (
<SelectGroup key={grp.label}>
<SelectLabel>{grp.label}</SelectLabel>
{grp.items.map((a: any) => (
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
))}
</SelectGroup>
)}
{expenseAccounts.length > 0 && (
<SelectGroup>
<SelectLabel>Expense</SelectLabel>
{expenseAccounts.map((a: any) => (
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
</Select>
</div>
@@ -999,19 +1001,20 @@ export default function AccountingBankingPage() {
)}
<div>
<Label>{txForm.type === "credit" ? "Income account *" : "Expense account *"}</Label>
<Label>Account / Category *</Label>
<Select value={txForm.coa_account_id} onValueChange={(v) => setTxForm({ ...txForm, coa_account_id: v })}>
<SelectTrigger><SelectValue placeholder={`Select ${txForm.type === "credit" ? "income" : "expense"} account`} /></SelectTrigger>
<SelectTrigger><SelectValue placeholder="Select an account" /></SelectTrigger>
<SelectContent>
{coaOptions.map((a: any) => (
<SelectItem key={a.id} value={a.id}>
{a.code ? `${a.code} · ` : ""}{a.name}
</SelectItem>
{coaGroupsFor(txForm.type).map((grp) => (
<SelectGroup key={grp.label}>
<SelectLabel>{grp.label}</SelectLabel>
{grp.items.map((a: any) => (
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
))}
{coaOptions.length === 0 && (
<SelectItem value="__none" disabled>
No {txForm.type === "credit" ? "income" : "expense"} accounts add them in Chart of Accounts
</SelectItem>
</SelectGroup>
))}
{coaGroupsFor(txForm.type).length === 0 && (
<SelectItem value="__none" disabled>No accounts add them in Chart of Accounts</SelectItem>
)}
</SelectContent>
</Select>
@@ -15,7 +15,7 @@ import {
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from "@/components/ui/dialog";
import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel,
} from "@/components/ui/select";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
import { ArrowLeft, CheckCircle2, AlertTriangle, FileDown, Search, Loader2, ArrowUp, ArrowDown, ChevronsUpDown, Plus, Ban, Pencil } from "lucide-react";
@@ -136,7 +136,7 @@ export default function AccountingReconcileDetailPage() {
queryKey: ["accounts", cid, "recon"],
enabled: !!cid,
queryFn: async () =>
(await accounting.from("accounts").select("id,name,type,is_bank").eq("company_id", cid).eq("is_archived", false).order("name")).data ?? [],
(await accounting.from("accounts").select("id,name,code,type,is_bank").eq("company_id", cid).eq("is_archived", false).order("code")).data ?? [],
});
const { data: vendors = [] } = useQuery({
@@ -856,15 +856,23 @@ export default function AccountingReconcileDetailPage() {
placeholder={addTx.type === "credit" ? "e.g. Interest earned" : "e.g. Bank service charge"} />
</div>
<div>
<Label>{addTx.type === "credit" ? "Income" : "Expense"} account (category)</Label>
<Label>Account / Category</Label>
<Select value={addTx.coa_account_id} onValueChange={(v) => setAddTx({ ...addTx, coa_account_id: v })}>
<SelectTrigger><SelectValue placeholder="Select account" /></SelectTrigger>
<SelectContent>
{(allAccounts as any[])
.filter((a: any) => a.id !== accountId && !a.is_bank)
.map((a: any) => (
<SelectItem key={a.id} value={a.id}>{a.name} ({a.type})</SelectItem>
{["income", "expense", "asset", "liability", "equity"].map((t) => {
const items = (allAccounts as any[]).filter((a: any) => a.type === t && a.id !== accountId);
if (!items.length) return null;
const label: Record<string, string> = { income: "Income", expense: "Expense", asset: "Assets", liability: "Liabilities", equity: "Equity" };
return (
<SelectGroup key={t}>
<SelectLabel>{label[t]}</SelectLabel>
{items.map((a: any) => (
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
))}
</SelectGroup>
);
})}
</SelectContent>
</Select>
</div>