mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
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:
@@ -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>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
{coaOptions.length === 0 && (
|
||||
<SelectItem value="__none" disabled>
|
||||
No {txForm.type === "credit" ? "income" : "expense"} accounts — add them in Chart of Accounts
|
||||
</SelectItem>
|
||||
{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>
|
||||
|
||||
Reference in New Issue
Block a user