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 bankAccounts = useMemo(() => (accounts as any[]).filter((a) => a.is_bank), [accounts]);
|
||||||
const incomeAccounts = useMemo(() => (accounts as any[]).filter((a) => a.type === "income"), [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]);
|
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({
|
const { data: vendors = [] } = useQuery({
|
||||||
queryKey: ["vendors-lookup", cid],
|
queryKey: ["vendors-lookup", cid],
|
||||||
@@ -541,7 +552,6 @@ export default function AccountingBankingPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const coaOptions = txForm.type === "credit" ? incomeAccounts : expenseAccounts;
|
|
||||||
|
|
||||||
const saveTransfer = async () => {
|
const saveTransfer = async () => {
|
||||||
const { from_account_id, to_account_id, date, memo } = transfer;
|
const { from_account_id, to_account_id, date, memo } = transfer;
|
||||||
@@ -797,22 +807,14 @@ export default function AccountingBankingPage() {
|
|||||||
<Select value="" onValueChange={bulkSetCategory}>
|
<Select value="" onValueChange={bulkSetCategory}>
|
||||||
<SelectTrigger className="h-8 w-56 text-sm"><SelectValue placeholder="Set category…" /></SelectTrigger>
|
<SelectTrigger className="h-8 w-56 text-sm"><SelectValue placeholder="Set category…" /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{incomeAccounts.length > 0 && (
|
{coaGroupsFor("debit").map((grp) => (
|
||||||
<SelectGroup>
|
<SelectGroup key={grp.label}>
|
||||||
<SelectLabel>Income</SelectLabel>
|
<SelectLabel>{grp.label}</SelectLabel>
|
||||||
{incomeAccounts.map((a: any) => (
|
{grp.items.map((a: any) => (
|
||||||
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
|
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</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>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@@ -999,19 +1001,20 @@ export default function AccountingBankingPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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 })}>
|
<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>
|
<SelectContent>
|
||||||
{coaOptions.map((a: any) => (
|
{coaGroupsFor(txForm.type).map((grp) => (
|
||||||
<SelectItem key={a.id} value={a.id}>
|
<SelectGroup key={grp.label}>
|
||||||
{a.code ? `${a.code} · ` : ""}{a.name}
|
<SelectLabel>{grp.label}</SelectLabel>
|
||||||
</SelectItem>
|
{grp.items.map((a: any) => (
|
||||||
|
<SelectItem key={a.id} value={a.id}>{a.code ? `${a.code} · ` : ""}{a.name}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
))}
|
))}
|
||||||
{coaOptions.length === 0 && (
|
{coaGroupsFor(txForm.type).length === 0 && (
|
||||||
<SelectItem value="__none" disabled>
|
<SelectItem value="__none" disabled>No accounts — add them in Chart of Accounts</SelectItem>
|
||||||
No {txForm.type === "credit" ? "income" : "expense"} accounts — add them in Chart of Accounts
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
|
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";
|
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"],
|
queryKey: ["accounts", cid, "recon"],
|
||||||
enabled: !!cid,
|
enabled: !!cid,
|
||||||
queryFn: async () =>
|
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({
|
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"} />
|
placeholder={addTx.type === "credit" ? "e.g. Interest earned" : "e.g. Bank service charge"} />
|
||||||
</div>
|
</div>
|
||||||
<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 })}>
|
<Select value={addTx.coa_account_id} onValueChange={(v) => setAddTx({ ...addTx, coa_account_id: v })}>
|
||||||
<SelectTrigger><SelectValue placeholder="Select account" /></SelectTrigger>
|
<SelectTrigger><SelectValue placeholder="Select account" /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{(allAccounts as any[])
|
{["income", "expense", "asset", "liability", "equity"].map((t) => {
|
||||||
.filter((a: any) => a.id !== accountId && !a.is_bank)
|
const items = (allAccounts as any[]).filter((a: any) => a.type === t && a.id !== accountId);
|
||||||
.map((a: any) => (
|
if (!items.length) return null;
|
||||||
<SelectItem key={a.id} value={a.id}>{a.name} ({a.type})</SelectItem>
|
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>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user