Accounting: prior-period reconcile items, void txns, unified report filters, accrual-only

1. Reconciliation now shows ALL outstanding (unreconciled) items on/before the
   statement date, including ones from prior periods — removed the prior-recon
   date floor. Finalized items still drop off (they carry a reconciliation_id).

2. Void transactions in Banking and Reconciliation. New accounting.transactions
   .voided flag (+ voided_at/by); voided rows stay visible (strikethrough + VOID
   badge) but are excluded from the running balance, register totals, cached
   account balance, and reconciliation. post_transaction_gl reverses the GL for
   gl_managed companies; un-void supported from Banking.

3. Unified report filters: the single Period bar on the Reports page now drives
   every report. General Ledger, Trial Balance, AR Aging (Property), Pre-Paid
   Homeowners, Cash Disbursement, and Reserve Fund no longer have their own date
   pickers — they consume the shared from/to (range) or to (as-of).

4. Accrual only: removed the cash-basis toggle from Trial Balance and General
   Ledger (the data was always accrual GL anyway; the cash label was misleading).
   All income/expense reports recognize on billed/issue date.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 10:51:03 -04:00
parent 5aef967b74
commit 6bf9da5482
10 changed files with 210 additions and 69 deletions
+40 -11
View File
@@ -13,7 +13,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGr
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table";
import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox";
import { Plus, Trash2, Pencil, ArrowLeftRight, Printer, Link2, RefreshCw, Unlink, FileUp, Download, Loader2 } from "lucide-react";
import { Plus, Trash2, Pencil, ArrowLeftRight, Printer, Link2, RefreshCw, Unlink, FileUp, Download, Loader2, Ban } from "lucide-react";
import { toast } from "sonner";
import { money, fmtDate } from "./lib/format";
import { PeriodPicker, periodRange, type PeriodPreset } from "./components/PeriodPicker";
@@ -206,8 +206,11 @@ export default function AccountingBankingPage() {
let bal = 0;
return (txs as any[]).map((tx) => {
const amt = Number(tx.amount ?? 0);
if (tx.type === "credit") bal += amt;
else bal -= amt;
// Voided rows stay visible for audit but don't move the running balance.
if (!tx.voided) {
if (tx.type === "credit") bal += amt;
else bal -= amt;
}
return { ...tx, running: bal };
});
}, [txs]);
@@ -482,6 +485,21 @@ export default function AccountingBankingPage() {
qc.invalidateQueries({ queryKey: ["accounts", cid] });
};
const toggleVoidTx = async (tx: any) => {
if (tx.reconciliation_id) return toast.error("Reconciled transactions can't be voided");
const next = !tx.voided;
if (next && !confirm("Void this transaction? It stays in the register for audit but is removed from the balance and can't be reconciled.")) return;
const patch = next
? { voided: true, voided_at: new Date().toISOString(), voided_by: user?.id ?? null }
: { voided: false, voided_at: null, voided_by: null };
const q = accounting.from("transactions").update(patch);
const { error } = tx.transfer_id ? await q.eq("transfer_id", tx.transfer_id) : await q.eq("id", tx.id);
if (error) return toast.error(error.message);
toast.success(next ? "Transaction voided" : "Void removed");
qc.invalidateQueries({ queryKey: ["transactions", cid] });
qc.invalidateQueries({ queryKey: ["accounts", cid] });
};
const openEdit = (tx: any) => {
if (tx.reconciliation_id) return toast.error("Reconciled transactions can't be edited");
setEditId(tx.id);
@@ -653,8 +671,8 @@ export default function AccountingBankingPage() {
? "Edit transaction"
: (txForm.type === "debit" ? "New payment" : "New transaction");
const totalDebits = filteredRegister.reduce((s, r) => s + (r.type === "debit" ? Number(r.amount) : 0), 0);
const totalCredits = filteredRegister.reduce((s, r) => s + (r.type === "credit" ? Number(r.amount) : 0), 0);
const totalDebits = filteredRegister.reduce((s, r) => s + (!r.voided && r.type === "debit" ? Number(r.amount) : 0), 0);
const totalCredits = filteredRegister.reduce((s, r) => s + (!r.voided && r.type === "credit" ? Number(r.amount) : 0), 0);
if (!associationId) return <p className="text-sm text-muted-foreground">Select an association.</p>;
if (companyLoading) return <div className="flex justify-center py-12"><Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /></div>;
@@ -849,14 +867,17 @@ export default function AccountingBankingPage() {
{row.reconciliation_id && (
<Badge variant="outline" className="text-[10px] py-0 px-1 bg-emerald-50 text-emerald-700 border-emerald-200">R</Badge>
)}
{row.description}
{row.voided && (
<Badge variant="outline" className="text-[10px] py-0 px-1 bg-red-50 text-red-700 border-red-200">VOID</Badge>
)}
<span className={row.voided ? "line-through text-muted-foreground" : ""}>{row.description}</span>
</span>
</TableCell>
<TableCell className="text-sm text-muted-foreground">{row.category ?? "—"}</TableCell>
<TableCell className="text-right text-sm text-red-600">
<TableCell className={`text-right text-sm text-red-600 ${row.voided ? "line-through opacity-60" : ""}`}>
{row.type === "debit" ? money(row.amount, cur) : ""}
</TableCell>
<TableCell className="text-right text-sm text-emerald-600">
<TableCell className={`text-right text-sm text-emerald-600 ${row.voided ? "line-through opacity-60" : ""}`}>
{row.type === "credit" ? money(row.amount, cur) : ""}
</TableCell>
<TableCell className={`text-right text-sm font-medium ${row.running < 0 ? "text-destructive" : ""}`}>
@@ -864,9 +885,17 @@ export default function AccountingBankingPage() {
</TableCell>
<TableCell>
<div className="flex justify-end gap-1">
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => openEdit(row)}>
<Pencil className="h-3.5 w-3.5" />
</Button>
{!row.voided && (
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={() => openEdit(row)}>
<Pencil className="h-3.5 w-3.5" />
</Button>
)}
{!row.reconciliation_id && (
<Button size="icon" variant="ghost" className={`h-7 w-7 ${row.voided ? "text-emerald-600 hover:text-emerald-700" : "text-amber-600 hover:text-amber-700"}`}
title={row.voided ? "Remove void" : "Void transaction"} onClick={() => toggleVoidTx(row)}>
<Ban className="h-3.5 w-3.5" />
</Button>
)}
<Button size="icon" variant="ghost" className="h-7 w-7 text-destructive hover:text-destructive" onClick={() => deleteTx(row)}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
@@ -18,7 +18,7 @@ import {
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
} 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 } from "lucide-react";
import { ArrowLeft, CheckCircle2, AlertTriangle, FileDown, Search, Loader2, ArrowUp, ArrowDown, ChevronsUpDown, Plus, Ban } from "lucide-react";
import { cn } from "@/lib/utils";
import { toast } from "sonner";
import { money, fmtDate } from "./lib/format";
@@ -52,6 +52,7 @@ type Tx = {
type: "debit" | "credit";
cleared: boolean;
reconciliation_id: string | null;
voided?: boolean;
};
export default function AccountingReconcileDetailPage() {
@@ -139,15 +140,18 @@ export default function AccountingReconcileDetailPage() {
queryKey: ["recon-txs", accountId, active?.statement_end_date, priorReconDate],
enabled: !!accountId && !!active,
queryFn: async () => {
let q = accounting
// Show every item not yet tied to a completed reconciliation, on/before the
// statement date — INCLUDING outstanding items from prior periods. Items
// that were finalized get a reconciliation_id and drop off; uncleared ones
// carry forward until they clear. Voided items are excluded entirely.
const { data } = await accounting
.from("transactions")
.select("id,date,description,reference,amount,type,cleared,reconciliation_id")
.select("id,date,description,reference,amount,type,cleared,reconciliation_id,voided")
.eq("account_id", accountId)
.is("reconciliation_id", null)
.lte("date", active!.statement_end_date);
// Only this period: skip anything already covered by the last reconciliation.
if (priorReconDate) q = q.gt("date", priorReconDate);
const { data } = await q.order("date");
.eq("voided", false)
.lte("date", active!.statement_end_date)
.order("date");
return (data ?? []) as Tx[];
},
});
@@ -378,6 +382,19 @@ export default function AccountingReconcileDetailPage() {
}
};
const voidTx = async (t: Tx) => {
if (t.reconciliation_id) return toast.error("Reconciled transactions can't be voided");
if (!confirm(`Void this ${t.type === "credit" ? "deposit" : "withdrawal"} of ${money(t.amount, cur)}? It will be removed from the register and this reconciliation.`)) return;
const { error } = await accounting.from("transactions")
.update({ voided: true, voided_at: new Date().toISOString(), voided_by: user?.id ?? null })
.eq("id", t.id);
if (error) return toast.error(error.message);
setChecked((prev) => { const n = new Set(prev); n.delete(t.id); return n; });
toast.success("Transaction voided");
qc.invalidateQueries({ queryKey: ["recon-txs", accountId] });
qc.invalidateQueries({ queryKey: ["transactions", cid] });
};
if (!associationId) return <p className="text-sm text-muted-foreground">Select an association.</p>;
if (companyLoading) return <div className="flex justify-center py-12"><Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /></div>;
if (companyError || !companyId) return <p className="text-sm text-muted-foreground text-center py-12">{companyError || "Accounting setup is not ready."}</p>;
@@ -454,6 +471,7 @@ export default function AccountingReconcileDetailPage() {
<SortHead col="reference" label="Ref #" sort={sort} onSort={toggleSort} />
<SortHead col="deposit" label="Deposit" sort={sort} onSort={toggleSort} align="right" />
<SortHead col="withdrawal" label="Withdrawal" sort={sort} onSort={toggleSort} align="right" />
<TableHead className="w-10 text-right">Void</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -485,11 +503,17 @@ export default function AccountingReconcileDetailPage() {
<TableCell className="text-right text-red-700">
{t.type === "debit" ? money(t.amount, cur) : ""}
</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground hover:text-destructive"
title="Void this transaction" onClick={() => voidTx(t)}>
<Ban className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
);
})}
{filtered.length === 0 && (
<TableRow><TableCell colSpan={6} className="text-center text-muted-foreground py-8">
<TableRow><TableCell colSpan={7} className="text-center text-muted-foreground py-8">
No unreconciled transactions in this period.
</TableCell></TableRow>
)}
@@ -657,22 +657,22 @@ export default function AccountingReportsPage({ association }: { association?: {
<IncomeStatementReport companyId={cid} companyName={associationName ?? "Company"} from={from} to={to} currency={cur} logoUrl={logoUrl} />
)}
{active === "trial-balance" && (
<TrialBalanceReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
<TrialBalanceReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} to={to} />
)}
{active === "general-ledger" && (
<GeneralLedgerReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} initialAccountId={drillAccountId} />
<GeneralLedgerReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} initialAccountId={drillAccountId} from={from} to={to} />
)}
{active === "reserve-fund" && (
<ReserveFundReport companyId={cid} companyName={associationName ?? ""} fiscalYearStart={fiscalYearStart} logoUrl={logoUrl} />
<ReserveFundReport companyId={cid} companyName={associationName ?? ""} fiscalYearStart={fiscalYearStart} logoUrl={logoUrl} to={to} />
)}
{active === "ar-aging-property" && (
<ARAgingPropertyReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
<ARAgingPropertyReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} to={to} />
)}
{active === "prepaid-homeowners" && (
<PrepaidHomeownersReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
<PrepaidHomeownersReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} to={to} />
)}
{active === "cash-disbursement" && (
<CashDisbursementReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} />
<CashDisbursementReport companyId={cid} companyName={associationName ?? ""} logoUrl={logoUrl} from={from} to={to} />
)}
{active === "reconciliation" && (
<ReportSheet title="Reconciliation Checks" companyName={associationName ?? "Company"} period={rangeLabel} logoUrl={logoUrl}>
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { supabase } from "@/integrations/supabase/client";
import { Card, CardContent } from "@/components/ui/card";
@@ -56,8 +56,9 @@ const dash = (n: number) => (n ? money(n) : "-");
* collection status, summary and distribution. Payments and credits apply to
* charges oldest-first (FIFO), so only genuinely open charge amounts age.
*/
export function ARAgingPropertyReport({ companyId, companyName, logoUrl }: { companyId: string; companyName: string; logoUrl?: string | null }) {
const [asOf, setAsOf] = useState(() => new Date().toISOString().slice(0, 10));
export function ARAgingPropertyReport({ companyId, companyName, logoUrl, to: propTo }: { companyId: string; companyName: string; logoUrl?: string | null; to?: string }) {
const [asOf, setAsOf] = useState(() => propTo ?? new Date().toISOString().slice(0, 10));
useEffect(() => { if (propTo) setAsOf(propTo); }, [propTo]);
const { data, isLoading } = useQuery({
queryKey: ["ar-aging-property", companyId, asOf],
@@ -305,7 +306,7 @@ export function ARAgingPropertyReport({ companyId, companyName, logoUrl }: { com
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<div>
<Label className="text-xs text-muted-foreground">As of</Label>
<Input type="date" value={asOf} onChange={(e) => setAsOf(e.target.value || asOf)} className="w-44 mt-1" />
<div className="mt-1 text-sm font-medium">{asOfLabel}</div>
</div>
{report && report.rows.length > 0 && (
<div className="ml-auto flex gap-2">
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { accounting } from "@/lib/accountingClient";
import { Card, CardContent } from "@/components/ui/card";
@@ -48,9 +48,10 @@ const acctLabel = (a: GLAccount | undefined) => (a ? `${a.code ? a.code + " - "
* platform-managed and Buildium-imported companies. Platform entries are
* enriched with check #, vendor and bill info from the banking register.
*/
export function CashDisbursementReport({ companyId, companyName, logoUrl }: { companyId: string; companyName: string; logoUrl?: string | null }) {
const [from, setFrom] = useState(monthStart());
const [to, setTo] = useState(today());
export function CashDisbursementReport({ companyId, companyName, logoUrl, from: propFrom, to: propTo }: { companyId: string; companyName: string; logoUrl?: string | null; from?: string; to?: string }) {
const [from, setFrom] = useState(propFrom ?? monthStart());
const [to, setTo] = useState(propTo ?? today());
useEffect(() => { if (propFrom) setFrom(propFrom); if (propTo) setTo(propTo); }, [propFrom, propTo]);
const { data, isLoading } = useQuery({
queryKey: ["cash-disbursement", companyId, from, to],
@@ -249,12 +250,8 @@ export function CashDisbursementReport({ companyId, companyName, logoUrl }: { co
<Card>
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<div>
<Label className="text-xs text-muted-foreground">From</Label>
<Input type="date" value={from} onChange={(e) => setFrom(e.target.value || from)} className="w-44 mt-1" />
</div>
<div>
<Label className="text-xs text-muted-foreground">To</Label>
<Input type="date" value={to} onChange={(e) => setTo(e.target.value || to)} className="w-44 mt-1" />
<Label className="text-xs text-muted-foreground">Period</Label>
<div className="mt-1 text-sm font-medium">{fmtDate(from)} {fmtDate(to)}</div>
</div>
{hasData && (
<div className="ml-auto flex gap-2">
@@ -38,10 +38,11 @@ type Txn = {
debit: number; credit: number; balance: number; abnormal?: boolean;
};
export function GeneralLedgerReport({ companyId, companyName, logoUrl, initialAccountId }: { companyId: string; companyName: string; logoUrl?: string | null; initialAccountId?: string | null }) {
export function GeneralLedgerReport({ companyId, companyName, logoUrl, initialAccountId, from: propFrom, to: propTo }: { companyId: string; companyName: string; logoUrl?: string | null; initialAccountId?: string | null; from?: string; to?: string }) {
const [preset, setPreset] = useState<PeriodPreset>("month");
const [from, setFrom] = useState(() => periodRange("month").from);
const [to, setTo] = useState(() => periodRange("month").to);
const [from, setFrom] = useState(() => propFrom ?? periodRange("month").from);
const [to, setTo] = useState(() => propTo ?? periodRange("month").to);
useEffect(() => { if (propFrom) setFrom(propFrom); if (propTo) setTo(propTo); }, [propFrom, propTo]);
const [selectedAccounts, setSelectedAccounts] = useState<string[]>(initialAccountId ? [initialAccountId] : []);
// When opened via a report drill-down, focus the chosen account.
@@ -287,10 +288,10 @@ export function GeneralLedgerReport({ companyId, companyName, logoUrl, initialAc
<div className="space-y-4">
<Card>
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<PeriodPicker
preset={preset} from={from} to={to}
onChange={(n) => { setPreset(n.preset); setFrom(n.from); setTo(n.to); }}
/>
<div>
<Label className="text-xs text-muted-foreground">Period</Label>
<div className="mt-1 text-sm font-medium">{fmtDate(from)} {fmtDate(to)}</div>
</div>
<div>
<Label className="text-xs text-muted-foreground">Accounts</Label>
<Popover>
@@ -323,11 +324,7 @@ export function GeneralLedgerReport({ companyId, companyName, logoUrl, initialAc
</div>
<div className="flex flex-col gap-1">
<Label className="text-xs text-muted-foreground">Basis</Label>
<div className="flex items-center gap-2 h-9 mt-1">
<span className={cn("text-sm", basis === "accrual" && "font-semibold")}>Accrual</span>
<Switch checked={basis === "cash"} onCheckedChange={(v) => setBasis(v ? "cash" : "accrual")} />
<span className={cn("text-sm", basis === "cash" && "font-semibold")}>Cash</span>
</div>
<div className="h-9 mt-1 flex items-center text-sm font-medium">Accrual</div>
</div>
<div className="flex flex-col gap-1">
<Label className="text-xs text-muted-foreground">Opening Row</Label>
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@@ -22,8 +22,9 @@ type Row = { account: string; property: string; ownerName: string; credit: numbe
* Buildium-style "Pre Paid Homeowners": every unit whose owner ledger nets to
* a credit (payments exceed charges) as of the chosen date, with the credit amount.
*/
export function PrepaidHomeownersReport({ companyId, companyName, logoUrl }: { companyId: string; companyName: string; logoUrl?: string | null }) {
const [asOf, setAsOf] = useState(() => new Date().toISOString().slice(0, 10));
export function PrepaidHomeownersReport({ companyId, companyName, logoUrl, to: propTo }: { companyId: string; companyName: string; logoUrl?: string | null; to?: string }) {
const [asOf, setAsOf] = useState(() => propTo ?? new Date().toISOString().slice(0, 10));
useEffect(() => { if (propTo) setAsOf(propTo); }, [propTo]);
const { data, isLoading } = useQuery({
queryKey: ["prepaid-homeowners", companyId, asOf],
@@ -126,7 +127,7 @@ export function PrepaidHomeownersReport({ companyId, companyName, logoUrl }: { c
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<div>
<Label className="text-xs text-muted-foreground">As of</Label>
<Input type="date" value={asOf} onChange={(e) => setAsOf(e.target.value || asOf)} className="w-44 mt-1" />
<div className="mt-1 text-sm font-medium">{asOfLabel}</div>
</div>
{rows.length > 0 && (
<div className="ml-auto flex gap-2">
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { accounting } from "@/lib/accountingClient";
import { Card, CardContent } from "@/components/ui/card";
@@ -61,13 +61,16 @@ export function ReserveFundReport({
companyName,
fiscalYearStart = "01-01",
logoUrl,
to: propTo,
}: {
companyId: string;
companyName: string;
fiscalYearStart?: string;
logoUrl?: string | null;
to?: string;
}) {
const [asOfMonth, setAsOfMonth] = useState(() => new Date().toISOString().slice(0, 7));
const [asOfMonth, setAsOfMonth] = useState(() => (propTo ?? new Date().toISOString().slice(0, 10)).slice(0, 7));
useEffect(() => { if (propTo) setAsOfMonth(propTo.slice(0, 7)); }, [propTo]);
const asOf = monthEnd(asOfMonth);
const monthStart = monthStartOf(asOfMonth);
@@ -270,7 +273,7 @@ export function ReserveFundReport({
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<div>
<Label className="text-xs text-muted-foreground">As of Month</Label>
<Input type="month" value={asOfMonth} onChange={(e) => setAsOfMonth(e.target.value || asOfMonth)} className="w-44 mt-1" />
<div className="mt-1 text-sm font-medium">{asOfLabel}</div>
</div>
<div className="text-xs text-muted-foreground pb-1">
Fiscal year from {new Date(fyStart + "T00:00:00").toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { accounting } from "@/lib/accountingClient";
import { Card, CardContent } from "@/components/ui/card";
@@ -52,8 +52,9 @@ function splitDebitCredit(a: Account): { debit: number; credit: number } {
return bal >= 0 ? { debit: 0, credit: bal } : { debit: -bal, credit: 0 };
}
export function TrialBalanceReport({ companyId, companyName, logoUrl }: { companyId: string; companyName: string; logoUrl?: string | null }) {
const [asOf, setAsOf] = useState(() => new Date().toISOString().slice(0, 10));
export function TrialBalanceReport({ companyId, companyName, logoUrl, to: propTo }: { companyId: string; companyName: string; logoUrl?: string | null; to?: string }) {
const [asOf, setAsOf] = useState(() => propTo ?? new Date().toISOString().slice(0, 10));
useEffect(() => { if (propTo) setAsOf(propTo); }, [propTo]);
const [basis, setBasis] = useState<"accrual" | "cash">("accrual");
const [showZero, setShowZero] = useState(false);
const [typeFilter, setTypeFilter] = useState<"all" | Account["type"]>("all");
@@ -210,17 +211,11 @@ export function TrialBalanceReport({ companyId, companyName, logoUrl }: { compan
<CardContent className="flex flex-wrap items-end gap-4 py-4">
<div>
<Label className="text-xs text-muted-foreground">As of Date</Label>
<Input type="date" value={asOf} onChange={(e) => setAsOf(e.target.value)} className="w-44 mt-1" />
<div className="mt-1 text-sm font-medium">{fmtDate(asOf)}</div>
</div>
<div>
<Label className="text-xs text-muted-foreground">Basis</Label>
<Select value={basis} onValueChange={(v) => setBasis(v as any)}>
<SelectTrigger className="w-32 mt-1"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="accrual">Accrual</SelectItem>
<SelectItem value="cash">Cash</SelectItem>
</SelectContent>
</Select>
<div className="mt-1 text-sm font-medium">Accrual</div>
</div>
<div>
<Label className="text-xs text-muted-foreground">Account Type</Label>