@burdenoff/microfe-proserviceworld 2026.720.1 → 2026.728.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proserviceworld/ProServiceWorldRoot.d.ts.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoot.js +49 -19
- package/dist/proserviceworld/ProServiceWorldRoot.js.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoutes.d.ts.map +1 -1
- package/dist/proserviceworld/ProServiceWorldRoutes.js +62 -27
- package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
- package/dist/proserviceworld/components/ui.d.ts.map +1 -1
- package/dist/proserviceworld/components/ui.js +7 -1
- package/dist/proserviceworld/components/ui.js.map +1 -1
- package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts +187 -0
- package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js +416 -140
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
- package/dist/proserviceworld/pages/ClientPortalAccessPage.d.ts +4 -0
- package/dist/proserviceworld/pages/ClientPortalAccessPage.d.ts.map +1 -0
- package/dist/proserviceworld/pages/ClientPortalAccessPage.js +191 -0
- package/dist/proserviceworld/pages/ClientPortalAccessPage.js.map +1 -0
- package/dist/proserviceworld/pages/DashboardPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +94 -82
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/MyPortalPage.d.ts +4 -0
- package/dist/proserviceworld/pages/MyPortalPage.d.ts.map +1 -0
- package/dist/proserviceworld/pages/MyPortalPage.js +162 -0
- package/dist/proserviceworld/pages/MyPortalPage.js.map +1 -0
- package/dist/proserviceworld/pages/MySignaturesPage.d.ts +4 -0
- package/dist/proserviceworld/pages/MySignaturesPage.d.ts.map +1 -0
- package/dist/proserviceworld/pages/MySignaturesPage.js +240 -0
- package/dist/proserviceworld/pages/MySignaturesPage.js.map +1 -0
- package/dist/proserviceworld/pages/SignaturesPage.d.ts +4 -0
- package/dist/proserviceworld/pages/SignaturesPage.d.ts.map +1 -0
- package/dist/proserviceworld/pages/SignaturesPage.js +317 -0
- package/dist/proserviceworld/pages/SignaturesPage.js.map +1 -0
- package/dist/proserviceworld/pages/TrustPage.d.ts +14 -0
- package/dist/proserviceworld/pages/TrustPage.d.ts.map +1 -0
- package/dist/proserviceworld/pages/TrustPage.js +603 -0
- package/dist/proserviceworld/pages/TrustPage.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignaturesPage.js","names":[],"sources":["../../../src/proserviceworld/pages/SignaturesPage.tsx"],"sourcesContent":["/**\n * Signatures — practitioner-side e-signature request management (BOFF-4212).\n *\n * Scope: a real capture-and-audit core, not a DocuSign-equivalent. Signers\n * are existing platform actors (workspace members or client-portal contacts\n * — see the \"Client Portal Access\" page for granting the latter) identified\n * by their platform user ID; there is no email-invite/magic-link flow for\n * non-platform signers here — that is documented, deferred scope.\n */\nimport type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { FileSignature, Plus, Send, XCircle, History, Trash2 } from 'lucide-react';\nimport {\n useSignatureRequests,\n useCreateSignatureRequest,\n useSendSignatureRequest,\n useCancelSignatureRequest,\n useSignatureAuditEvents,\n useDocuments,\n type PswSignatureRequest,\n} from '../hooks/useProServiceWorldApi';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n TableWrap,\n Th,\n Td,\n fmtDate,\n} from '../components/ui';\n\nconst ACTIVE_STATUSES = new Set(['DRAFT', 'SENT', 'VIEWED', 'PARTIALLY_SIGNED']);\n\nexport const SignaturesPage: FC = () => {\n const { data, isLoading, error, refetch } = useSignatureRequests();\n const requests = data?.items ?? [];\n const [creating, setCreating] = useState(false);\n const [auditFor, setAuditFor] = useState<PswSignatureRequest | null>(null);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n const send = useSendSignatureRequest();\n const cancel = useCancelSignatureRequest();\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Signatures\"\n subtitle=\"Request, track, and audit e-signatures on your practice's documents.\"\n icon={<FileSignature className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New signature request\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : requests.length === 0 ? (\n <Empty\n title=\"No signature requests\"\n description=\"Request a signature on an engagement letter or other document.\"\n icon={<FileSignature className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New signature request\n </Button>\n }\n />\n ) : (\n <SectionCard\n title={`${data?.totalCount ?? requests.length} request${(data?.totalCount ?? requests.length) === 1 ? '' : 's'}`}\n >\n <TableWrap>\n <thead>\n <tr>\n <Th>Document</Th>\n <Th>Status</Th>\n <Th>Signers</Th>\n <Th>Sent</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {requests.map((r) => (\n <tr key={r.id} className=\"hover:bg-bg-sunken/50\">\n <Td>\n <span className=\"font-medium text-text-primary\">\n {r.document?.name ?? r.documentId}\n </span>\n </Td>\n <Td>\n <StatusPill status={r.status} />\n </Td>\n <Td>\n <div className=\"flex flex-col gap-0.5\">\n {r.signers.map((s) => (\n <span key={s.id} className=\"text-xs text-text-secondary\">\n {s.name} — <StatusPill status={s.status} />\n </span>\n ))}\n </div>\n </Td>\n <Td>{fmtDate(r.sentAt)}</Td>\n <Td className=\"text-right\">\n <div className=\"flex justify-end gap-1\">\n {r.status === 'DRAFT' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Send\"\n disabled={send.isPending}\n onClick={() =>\n send.mutate(r.id, {\n onSuccess: () =>\n setBanner({ tone: 'success', text: 'Signature request sent.' }),\n onError: (e) =>\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : 'Failed.',\n }),\n })\n }\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n )}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Audit trail\"\n onClick={() => setAuditFor(r)}\n >\n <History className=\"h-4 w-4\" />\n </Button>\n {ACTIVE_STATUSES.has(r.status) && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Cancel\"\n disabled={cancel.isPending}\n onClick={() =>\n cancel.mutate(r.id, {\n onSuccess: () =>\n setBanner({\n tone: 'success',\n text: 'Signature request cancelled.',\n }),\n onError: (e) =>\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : 'Failed.',\n }),\n })\n }\n >\n <XCircle className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <NewSignatureRequestModal\n onClose={() => setCreating(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Signature request created and sent.' });\n }}\n onFail={(msg) => setBanner({ tone: 'danger', text: msg })}\n />\n )}\n\n {auditFor && <AuditTrailModal request={auditFor} onClose={() => setAuditFor(null)} />}\n </div>\n );\n};\n\nconst AuditTrailModal: FC<{ request: PswSignatureRequest; onClose: () => void }> = ({\n request,\n onClose,\n}) => {\n const { data, isLoading, error } = useSignatureAuditEvents(request.id);\n return (\n <Modal\n open\n onClose={onClose}\n title={`Audit trail — ${request.document?.name ?? request.documentId}`}\n >\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} />\n ) : !data || data.items.length === 0 ? (\n <Empty title=\"No events yet\" icon={<History className=\"h-8 w-8\" />} />\n ) : (\n <ul className=\"space-y-2 text-sm\">\n {data.items.map((e) => (\n <li key={e.id} className=\"rounded-lg border border-border-seam p-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium text-text-primary\">{e.eventType}</span>\n <span className=\"text-text-tertiary\">{fmtDate(e.createdAt)}</span>\n </div>\n <div className=\"text-text-secondary\">\n {e.actorLabel}\n {e.ipAddress ? ` · ${e.ipAddress}` : ''}\n {e.documentVersion != null ? ` · doc v${e.documentVersion}` : ''}\n </div>\n </li>\n ))}\n </ul>\n )}\n </Modal>\n );\n};\n\ninterface SignerDraft {\n actorId: string;\n name: string;\n email: string;\n}\n\nconst NewSignatureRequestModal: FC<{\n onClose: () => void;\n onDone: () => void;\n onFail: (msg: string) => void;\n}> = ({ onClose, onDone, onFail }) => {\n const { data: documents } = useDocuments();\n const create = useCreateSignatureRequest();\n const send = useSendSignatureRequest();\n const [documentId, setDocumentId] = useState('');\n const [message, setMessage] = useState('');\n const [signers, setSigners] = useState<SignerDraft[]>([{ actorId: '', name: '', email: '' }]);\n const [err, setErr] = useState<string | null>(null);\n\n const updateSigner = (i: number, patch: Partial<SignerDraft>) =>\n setSigners((prev) => prev.map((s, idx) => (idx === i ? { ...s, ...patch } : s)));\n\n const submit = async () => {\n setErr(null);\n if (!documentId) return setErr('Choose a document.');\n const validSigners = signers.filter((s) => s.actorId.trim() && s.name.trim() && s.email.trim());\n if (validSigners.length === 0)\n return setErr('Add at least one signer (platform user ID, name, email).');\n\n create.mutate(\n {\n documentId,\n message: message.trim() || null,\n signers: validSigners.map((s) => ({\n actorId: s.actorId.trim(),\n name: s.name.trim(),\n email: s.email.trim(),\n })),\n },\n {\n onSuccess: (result) => {\n const created = result as { pswCreateSignatureRequest?: { id: string } };\n const id = created.pswCreateSignatureRequest?.id;\n if (!id) {\n onDone();\n onClose();\n return;\n }\n send.mutate(id, {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => onFail(e instanceof Error ? e.message : 'Created but failed to send.'),\n });\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n const busy = create.isPending || send.isPending;\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"New signature request\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={busy}>\n Cancel\n </Button>\n <Button onClick={() => void submit()} disabled={busy}>\n {busy ? 'Sending…' : 'Create & send'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Document\" required>\n <NativeSelect value={documentId} onChange={(e) => setDocumentId(e.target.value)}>\n <option value=\"\">Select a document…</option>\n {(documents?.items ?? []).map((d) => (\n <option key={d.id} value={d.id}>\n {d.name} (v{d.version})\n </option>\n ))}\n </NativeSelect>\n </Field>\n <Field label=\"Message\" hint=\"Optional note shown to signers.\">\n <TextArea value={message} onChange={(e) => setMessage(e.target.value)} />\n </Field>\n <Field\n label=\"Signers\"\n hint=\"Each signer must already be a platform actor — a workspace member or a client-portal contact (see Client Portal Access for their platform user ID).\"\n >\n <div className=\"space-y-2\">\n {signers.map((s, i) => (\n <div key={i} className=\"grid grid-cols-3 gap-2\">\n <TextInput\n placeholder=\"Platform user ID\"\n value={s.actorId}\n onChange={(e) => updateSigner(i, { actorId: e.target.value })}\n />\n <TextInput\n placeholder=\"Name\"\n value={s.name}\n onChange={(e) => updateSigner(i, { name: e.target.value })}\n />\n <TextInput\n placeholder=\"Email\"\n value={s.email}\n onChange={(e) => updateSigner(i, { email: e.target.value })}\n />\n </div>\n ))}\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() =>\n setSigners((prev) => [...prev, { actorId: '', name: '', email: '' }])\n }\n >\n <Plus className=\"mr-1 h-3.5 w-3.5\" />\n Add signer\n </Button>\n {signers.length > 1 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setSigners((prev) => prev.slice(0, -1))}\n >\n <Trash2 className=\"mr-1 h-3.5 w-3.5\" />\n Remove last\n </Button>\n )}\n </div>\n </div>\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trust accounting / IOLTA reconciliation (BOFF-4209).
|
|
5
|
+
*
|
|
6
|
+
* A firm's pooled trust account holds client funds separately from its own
|
|
7
|
+
* operating money. Every client's subsidiary-ledger balance is enforced
|
|
8
|
+
* server-side to never go negative (see `PracticeService.writeTrustLedgerEntry`
|
|
9
|
+
* in wspace-proserviceworld-svc) — this page surfaces that ledger, lets a
|
|
10
|
+
* billing/compliance user record deposits/withdrawals/transfers, and run a
|
|
11
|
+
* three-way reconciliation against a bank statement balance.
|
|
12
|
+
*/
|
|
13
|
+
export declare const TrustPage: FC;
|
|
14
|
+
//# sourceMappingURL=TrustPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrustPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/TrustPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA+ChC;;;;;;;;;GASG;AACH,eAAO,MAAM,SAAS,EAAE,EAmTvB,CAAC"}
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { useClients as e, useCreateTrustAccount as t, useCreateTrustTransfer as n, useReconcileTrustAccount as r, useRecordTrustDeposit as i, useRecordTrustWithdrawal as a, useTrustAccounts as o, useTrustClientBalance as s, useTrustTransactions as c } from "../hooks/useProServiceWorldApi.js";
|
|
2
|
+
import { cn as l } from "../utils/cn.js";
|
|
3
|
+
import { Banner as u, Empty as d, ErrorState as f, Field as p, LoadingBlock as m, MetricCard as h, Modal as g, NativeSelect as _, PageHeader as v, SectionCard as y, StatusPill as b, TableWrap as x, Td as S, TextArea as C, TextInput as w, Th as T, fmtDate as E, fmtMoney as D } from "../components/ui.js";
|
|
4
|
+
import { useMemo as O, useState as k } from "react";
|
|
5
|
+
import { ArrowDownCircle as A, ArrowUpCircle as j, ClipboardCheck as M, Plus as N, Repeat as P, ShieldAlert as F, Wallet as I } from "lucide-react";
|
|
6
|
+
import { Fragment as L, jsx as R, jsxs as z } from "react/jsx-runtime";
|
|
7
|
+
import { Button as B } from "@burdenoff/fe-libs/ui";
|
|
8
|
+
//#region src/proserviceworld/pages/TrustPage.tsx
|
|
9
|
+
var V = () => {
|
|
10
|
+
let [t, n] = k(""), [r, i] = k(""), [a, p] = k(null), [g, C] = k(null), [w, V] = k(null), K = o(), q = e({ pagination: { limit: 100 } }), J = O(() => new Map((q.data?.items ?? []).map((e) => [e.id, e.name])), [q.data]), Y = t || K.data?.items[0]?.id || "", X = c({ trustAccountId: Y || void 0 }), Z = s(Y || void 0, r || void 0);
|
|
11
|
+
if (K.isLoading) return /* @__PURE__ */ R(m, {});
|
|
12
|
+
if (K.error) return /* @__PURE__ */ R(f, {
|
|
13
|
+
error: K.error,
|
|
14
|
+
onRetry: () => void K.refetch()
|
|
15
|
+
});
|
|
16
|
+
let Q = K.data?.items.find((e) => e.id === Y);
|
|
17
|
+
return /* @__PURE__ */ z("div", {
|
|
18
|
+
className: "space-y-6",
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ R(v, {
|
|
21
|
+
title: "Trust Accounting",
|
|
22
|
+
subtitle: "IOLTA-style trust ledger — client funds held separately from the firm's operating money, reconciled against the bank statement.",
|
|
23
|
+
icon: /* @__PURE__ */ R(I, { className: "h-6 w-6" }),
|
|
24
|
+
actions: /* @__PURE__ */ z(B, {
|
|
25
|
+
onClick: () => p("account"),
|
|
26
|
+
children: [/* @__PURE__ */ R(N, { className: "mr-1.5 h-4 w-4" }), "New trust account"]
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
g && /* @__PURE__ */ R(u, {
|
|
30
|
+
tone: g.tone,
|
|
31
|
+
onClose: () => C(null),
|
|
32
|
+
children: g.text
|
|
33
|
+
}),
|
|
34
|
+
!K.data || K.data.items.length === 0 ? /* @__PURE__ */ R(d, {
|
|
35
|
+
title: "No trust accounts yet",
|
|
36
|
+
description: "Open a trust (IOLTA) account to start holding client funds separately from operating money.",
|
|
37
|
+
icon: /* @__PURE__ */ R(I, { className: "h-8 w-8" }),
|
|
38
|
+
action: /* @__PURE__ */ z(B, {
|
|
39
|
+
onClick: () => p("account"),
|
|
40
|
+
children: [/* @__PURE__ */ R(N, { className: "mr-1.5 h-4 w-4" }), "New trust account"]
|
|
41
|
+
})
|
|
42
|
+
}) : /* @__PURE__ */ z(L, { children: [
|
|
43
|
+
/* @__PURE__ */ z("div", {
|
|
44
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
45
|
+
children: [/* @__PURE__ */ z("div", {
|
|
46
|
+
className: "flex flex-wrap items-center gap-2",
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ R(_, {
|
|
49
|
+
value: Y,
|
|
50
|
+
onChange: (e) => n(e.target.value),
|
|
51
|
+
className: "sm:w-64",
|
|
52
|
+
children: K.data.items.map((e) => /* @__PURE__ */ z("option", {
|
|
53
|
+
value: e.id,
|
|
54
|
+
children: [
|
|
55
|
+
e.name,
|
|
56
|
+
" ",
|
|
57
|
+
e.status === "CLOSED" ? "(closed)" : ""
|
|
58
|
+
]
|
|
59
|
+
}, e.id))
|
|
60
|
+
}),
|
|
61
|
+
Q && /* @__PURE__ */ R(b, { status: Q.status }),
|
|
62
|
+
/* @__PURE__ */ z(_, {
|
|
63
|
+
value: r,
|
|
64
|
+
onChange: (e) => i(e.target.value),
|
|
65
|
+
className: "sm:w-56",
|
|
66
|
+
children: [/* @__PURE__ */ R("option", {
|
|
67
|
+
value: "",
|
|
68
|
+
children: "All clients"
|
|
69
|
+
}), q.data?.items.map((e) => /* @__PURE__ */ R("option", {
|
|
70
|
+
value: e.id,
|
|
71
|
+
children: e.name
|
|
72
|
+
}, e.id))]
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
}), /* @__PURE__ */ z("div", {
|
|
76
|
+
className: "flex flex-wrap gap-2",
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ z(B, {
|
|
79
|
+
variant: "outline",
|
|
80
|
+
size: "sm",
|
|
81
|
+
onClick: () => p("deposit"),
|
|
82
|
+
children: [/* @__PURE__ */ R(A, { className: "mr-1.5 h-4 w-4" }), "Deposit"]
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ z(B, {
|
|
85
|
+
variant: "outline",
|
|
86
|
+
size: "sm",
|
|
87
|
+
onClick: () => p("withdrawal"),
|
|
88
|
+
children: [/* @__PURE__ */ R(j, { className: "mr-1.5 h-4 w-4" }), "Withdrawal"]
|
|
89
|
+
}),
|
|
90
|
+
/* @__PURE__ */ z(B, {
|
|
91
|
+
variant: "outline",
|
|
92
|
+
size: "sm",
|
|
93
|
+
onClick: () => p("transfer"),
|
|
94
|
+
children: [/* @__PURE__ */ R(P, { className: "mr-1.5 h-4 w-4" }), "Transfer to operating"]
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ z(B, {
|
|
97
|
+
variant: "outline",
|
|
98
|
+
size: "sm",
|
|
99
|
+
onClick: () => p("reconcile"),
|
|
100
|
+
children: [/* @__PURE__ */ R(M, { className: "mr-1.5 h-4 w-4" }), "Reconcile"]
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
})]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ z("div", {
|
|
106
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-3",
|
|
107
|
+
children: [
|
|
108
|
+
/* @__PURE__ */ R(h, {
|
|
109
|
+
label: "Trust accounts",
|
|
110
|
+
value: K.data.totalCount,
|
|
111
|
+
icon: /* @__PURE__ */ R(I, { className: "h-4 w-4" })
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ R(h, {
|
|
114
|
+
label: "Currency",
|
|
115
|
+
value: Q?.currency ?? "—",
|
|
116
|
+
icon: /* @__PURE__ */ R(I, { className: "h-4 w-4" })
|
|
117
|
+
}),
|
|
118
|
+
r ? /* @__PURE__ */ R(h, {
|
|
119
|
+
label: `Balance — ${J.get(r) ?? "client"}`,
|
|
120
|
+
value: Z.isLoading ? "…" : D(Z.data?.balance ?? 0, Q?.currency),
|
|
121
|
+
icon: /* @__PURE__ */ R(I, { className: "h-4 w-4" }),
|
|
122
|
+
tone: (Z.data?.balance ?? 0) < 0 ? "danger" : "success"
|
|
123
|
+
}) : /* @__PURE__ */ R(h, {
|
|
124
|
+
label: "Ledger entries",
|
|
125
|
+
value: X.data?.totalCount ?? 0,
|
|
126
|
+
icon: /* @__PURE__ */ R(M, { className: "h-4 w-4" })
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
}),
|
|
130
|
+
w && /* @__PURE__ */ z(y, {
|
|
131
|
+
title: "Reconciliation result",
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ z("div", {
|
|
134
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-4",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ R(h, {
|
|
137
|
+
label: "Book balance",
|
|
138
|
+
value: D(w.bookBalance, Q?.currency),
|
|
139
|
+
icon: /* @__PURE__ */ R(I, { className: "h-4 w-4" })
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ R(h, {
|
|
142
|
+
label: "Statement balance",
|
|
143
|
+
value: w.statementBalance == null ? "—" : D(w.statementBalance, Q?.currency),
|
|
144
|
+
icon: /* @__PURE__ */ R(M, { className: "h-4 w-4" })
|
|
145
|
+
}),
|
|
146
|
+
/* @__PURE__ */ R(h, {
|
|
147
|
+
label: "Difference",
|
|
148
|
+
value: w.difference == null ? "—" : D(w.difference, Q?.currency),
|
|
149
|
+
tone: w.inBalance ? "success" : "danger",
|
|
150
|
+
icon: w.inBalance ? /* @__PURE__ */ R(M, { className: "h-4 w-4" }) : /* @__PURE__ */ R(F, { className: "h-4 w-4" })
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ R(h, {
|
|
153
|
+
label: "Reconciled entries",
|
|
154
|
+
value: w.reconciledCount,
|
|
155
|
+
icon: /* @__PURE__ */ R(M, { className: "h-4 w-4" })
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
}),
|
|
159
|
+
!w.inBalance && /* @__PURE__ */ R("div", {
|
|
160
|
+
className: "mt-3",
|
|
161
|
+
children: /* @__PURE__ */ R(u, {
|
|
162
|
+
tone: "danger",
|
|
163
|
+
children: "Out of balance — the bank statement does not match the book balance. Investigate before closing this reconciliation period."
|
|
164
|
+
})
|
|
165
|
+
}),
|
|
166
|
+
w.clientBalances.length > 0 && /* @__PURE__ */ R("div", {
|
|
167
|
+
className: "mt-3",
|
|
168
|
+
children: /* @__PURE__ */ z(x, { children: [/* @__PURE__ */ R("thead", { children: /* @__PURE__ */ z("tr", { children: [
|
|
169
|
+
/* @__PURE__ */ R(T, { children: "Client" }),
|
|
170
|
+
/* @__PURE__ */ R(T, { children: "Matter" }),
|
|
171
|
+
/* @__PURE__ */ R(T, {
|
|
172
|
+
className: "text-right",
|
|
173
|
+
children: "Balance"
|
|
174
|
+
})
|
|
175
|
+
] }) }), /* @__PURE__ */ R("tbody", { children: w.clientBalances.map((e, t) => /* @__PURE__ */ z("tr", { children: [
|
|
176
|
+
/* @__PURE__ */ R(S, { children: J.get(e.clientId) ?? e.clientId }),
|
|
177
|
+
/* @__PURE__ */ R(S, { children: e.matterId ?? "—" }),
|
|
178
|
+
/* @__PURE__ */ R(S, {
|
|
179
|
+
className: "text-right font-medium",
|
|
180
|
+
children: D(e.balance, Q?.currency)
|
|
181
|
+
})
|
|
182
|
+
] }, `${e.clientId}-${e.matterId ?? "none"}-${t}`)) })] })
|
|
183
|
+
})
|
|
184
|
+
]
|
|
185
|
+
}),
|
|
186
|
+
X.isLoading ? /* @__PURE__ */ R(m, {}) : X.error ? /* @__PURE__ */ R(f, {
|
|
187
|
+
error: X.error,
|
|
188
|
+
onRetry: () => void X.refetch()
|
|
189
|
+
}) : !X.data || X.data.items.length === 0 ? /* @__PURE__ */ R(d, {
|
|
190
|
+
title: "No trust ledger entries yet",
|
|
191
|
+
icon: /* @__PURE__ */ R(I, { className: "h-8 w-8" })
|
|
192
|
+
}) : /* @__PURE__ */ R(y, {
|
|
193
|
+
title: "Trust ledger",
|
|
194
|
+
children: /* @__PURE__ */ z(x, { children: [/* @__PURE__ */ R("thead", { children: /* @__PURE__ */ z("tr", { children: [
|
|
195
|
+
/* @__PURE__ */ R(T, { children: "Date" }),
|
|
196
|
+
/* @__PURE__ */ R(T, { children: "Client" }),
|
|
197
|
+
/* @__PURE__ */ R(T, { children: "Type" }),
|
|
198
|
+
/* @__PURE__ */ R(T, {
|
|
199
|
+
className: "text-right",
|
|
200
|
+
children: "Amount"
|
|
201
|
+
}),
|
|
202
|
+
/* @__PURE__ */ R(T, {
|
|
203
|
+
className: "text-right",
|
|
204
|
+
children: "Balance after"
|
|
205
|
+
}),
|
|
206
|
+
/* @__PURE__ */ R(T, { children: "Reference" }),
|
|
207
|
+
/* @__PURE__ */ R(T, { children: "Reconciled" })
|
|
208
|
+
] }) }), /* @__PURE__ */ R("tbody", { children: X.data.items.map((e) => /* @__PURE__ */ z("tr", {
|
|
209
|
+
className: "transition-colors hover:bg-bg-sunken/60",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ R(S, {
|
|
212
|
+
className: "whitespace-nowrap",
|
|
213
|
+
children: E(e.createdAt)
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ R(S, { children: J.get(e.clientId) ?? e.clientId }),
|
|
216
|
+
/* @__PURE__ */ R(S, { children: /* @__PURE__ */ R(b, { status: e.type }) }),
|
|
217
|
+
/* @__PURE__ */ R(S, {
|
|
218
|
+
className: l("text-right font-medium whitespace-nowrap", e.amount < 0 ? "text-status-error-text" : "text-status-success-text"),
|
|
219
|
+
children: D(e.amount, Q?.currency)
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ R(S, {
|
|
222
|
+
className: "text-right whitespace-nowrap",
|
|
223
|
+
children: D(e.balanceAfter, Q?.currency)
|
|
224
|
+
}),
|
|
225
|
+
/* @__PURE__ */ R(S, { children: e.reference ?? "—" }),
|
|
226
|
+
/* @__PURE__ */ R(S, { children: e.reconciled ? /* @__PURE__ */ R(b, { status: "RECONCILED" }) : "—" })
|
|
227
|
+
]
|
|
228
|
+
}, e.id)) })] })
|
|
229
|
+
})
|
|
230
|
+
] }),
|
|
231
|
+
a === "account" && /* @__PURE__ */ R(H, {
|
|
232
|
+
onClose: () => p(null),
|
|
233
|
+
onSaved: (e) => C({
|
|
234
|
+
tone: "success",
|
|
235
|
+
text: `${e} created.`
|
|
236
|
+
})
|
|
237
|
+
}),
|
|
238
|
+
a === "deposit" && Y && /* @__PURE__ */ R(U, {
|
|
239
|
+
kind: "deposit",
|
|
240
|
+
trustAccountId: Y,
|
|
241
|
+
currency: Q?.currency,
|
|
242
|
+
onClose: () => p(null),
|
|
243
|
+
onSaved: () => C({
|
|
244
|
+
tone: "success",
|
|
245
|
+
text: "Deposit recorded."
|
|
246
|
+
})
|
|
247
|
+
}),
|
|
248
|
+
a === "withdrawal" && Y && /* @__PURE__ */ R(U, {
|
|
249
|
+
kind: "withdrawal",
|
|
250
|
+
trustAccountId: Y,
|
|
251
|
+
currency: Q?.currency,
|
|
252
|
+
onClose: () => p(null),
|
|
253
|
+
onSaved: () => C({
|
|
254
|
+
tone: "success",
|
|
255
|
+
text: "Withdrawal recorded."
|
|
256
|
+
})
|
|
257
|
+
}),
|
|
258
|
+
a === "transfer" && Y && /* @__PURE__ */ R(W, {
|
|
259
|
+
trustAccountId: Y,
|
|
260
|
+
currency: Q?.currency,
|
|
261
|
+
onClose: () => p(null),
|
|
262
|
+
onSaved: () => C({
|
|
263
|
+
tone: "success",
|
|
264
|
+
text: "Transfer to operating recorded."
|
|
265
|
+
})
|
|
266
|
+
}),
|
|
267
|
+
a === "reconcile" && Y && /* @__PURE__ */ R(G, {
|
|
268
|
+
trustAccountId: Y,
|
|
269
|
+
onClose: () => p(null),
|
|
270
|
+
onSaved: (e) => {
|
|
271
|
+
V(e), C({
|
|
272
|
+
tone: e.inBalance ? "success" : "danger",
|
|
273
|
+
text: e.inBalance ? `Reconciled — ${e.reconciledCount} entries, in balance.` : `Reconciled with a difference of ${D(e.difference ?? 0, Q?.currency)} — review before closing the period.`
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
]
|
|
278
|
+
});
|
|
279
|
+
}, H = ({ onClose: e, onSaved: n }) => {
|
|
280
|
+
let r = t(), [i, a] = k({
|
|
281
|
+
name: "",
|
|
282
|
+
institutionName: "",
|
|
283
|
+
accountNumberLast4: "",
|
|
284
|
+
currency: "USD"
|
|
285
|
+
}), [o, s] = k(null), c = (e) => (t) => a((n) => ({
|
|
286
|
+
...n,
|
|
287
|
+
[e]: t.target.value
|
|
288
|
+
}));
|
|
289
|
+
return /* @__PURE__ */ R(g, {
|
|
290
|
+
open: !0,
|
|
291
|
+
onClose: e,
|
|
292
|
+
title: "New trust account",
|
|
293
|
+
footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
|
|
294
|
+
variant: "outline",
|
|
295
|
+
onClick: e,
|
|
296
|
+
disabled: r.isPending,
|
|
297
|
+
children: "Cancel"
|
|
298
|
+
}), /* @__PURE__ */ R(B, {
|
|
299
|
+
onClick: () => {
|
|
300
|
+
if (s(null), !i.name.trim()) return s("Account name is required.");
|
|
301
|
+
r.mutate({
|
|
302
|
+
name: i.name.trim(),
|
|
303
|
+
institutionName: i.institutionName.trim() || null,
|
|
304
|
+
accountNumberLast4: i.accountNumberLast4.trim() || null,
|
|
305
|
+
currency: i.currency || "USD"
|
|
306
|
+
}, {
|
|
307
|
+
onSuccess: () => {
|
|
308
|
+
n(i.name.trim()), e();
|
|
309
|
+
},
|
|
310
|
+
onError: (e) => s(e instanceof Error ? e.message : "Failed.")
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
disabled: r.isPending,
|
|
314
|
+
children: r.isPending ? "Creating…" : "Create account"
|
|
315
|
+
})] }),
|
|
316
|
+
children: /* @__PURE__ */ z("div", {
|
|
317
|
+
className: "space-y-3",
|
|
318
|
+
children: [
|
|
319
|
+
o && /* @__PURE__ */ R(u, {
|
|
320
|
+
tone: "danger",
|
|
321
|
+
children: o
|
|
322
|
+
}),
|
|
323
|
+
/* @__PURE__ */ R(p, {
|
|
324
|
+
label: "Name",
|
|
325
|
+
required: !0,
|
|
326
|
+
children: /* @__PURE__ */ R(w, {
|
|
327
|
+
value: i.name,
|
|
328
|
+
onChange: c("name"),
|
|
329
|
+
placeholder: "IOLTA Main"
|
|
330
|
+
})
|
|
331
|
+
}),
|
|
332
|
+
/* @__PURE__ */ z("div", {
|
|
333
|
+
className: "grid grid-cols-2 gap-3",
|
|
334
|
+
children: [/* @__PURE__ */ R(p, {
|
|
335
|
+
label: "Institution (optional)",
|
|
336
|
+
children: /* @__PURE__ */ R(w, {
|
|
337
|
+
value: i.institutionName,
|
|
338
|
+
onChange: c("institutionName")
|
|
339
|
+
})
|
|
340
|
+
}), /* @__PURE__ */ R(p, {
|
|
341
|
+
label: "Currency",
|
|
342
|
+
children: /* @__PURE__ */ R(w, {
|
|
343
|
+
value: i.currency,
|
|
344
|
+
onChange: c("currency"),
|
|
345
|
+
maxLength: 3
|
|
346
|
+
})
|
|
347
|
+
})]
|
|
348
|
+
}),
|
|
349
|
+
/* @__PURE__ */ R(p, {
|
|
350
|
+
label: "Last 4 of account number (optional)",
|
|
351
|
+
children: /* @__PURE__ */ R(w, {
|
|
352
|
+
value: i.accountNumberLast4,
|
|
353
|
+
onChange: c("accountNumberLast4"),
|
|
354
|
+
maxLength: 4
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
]
|
|
358
|
+
})
|
|
359
|
+
});
|
|
360
|
+
}, U = ({ kind: t, trustAccountId: n, currency: r, onClose: o, onSaved: s }) => {
|
|
361
|
+
let c = i(), l = a(), d = t === "deposit" ? c : l, f = e({ pagination: { limit: 100 } }), [m, h] = k({
|
|
362
|
+
clientId: "",
|
|
363
|
+
matterId: "",
|
|
364
|
+
amount: "",
|
|
365
|
+
reference: "",
|
|
366
|
+
description: ""
|
|
367
|
+
}), [v, y] = k(null), b = (e) => (t) => h((n) => ({
|
|
368
|
+
...n,
|
|
369
|
+
[e]: t.target.value
|
|
370
|
+
}));
|
|
371
|
+
return /* @__PURE__ */ R(g, {
|
|
372
|
+
open: !0,
|
|
373
|
+
onClose: o,
|
|
374
|
+
title: t === "deposit" ? "Record trust deposit" : "Record trust withdrawal",
|
|
375
|
+
footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
|
|
376
|
+
variant: "outline",
|
|
377
|
+
onClick: o,
|
|
378
|
+
disabled: d.isPending,
|
|
379
|
+
children: "Cancel"
|
|
380
|
+
}), /* @__PURE__ */ R(B, {
|
|
381
|
+
onClick: () => {
|
|
382
|
+
if (y(null), !m.clientId) return y("Select a client.");
|
|
383
|
+
let e = Number(m.amount);
|
|
384
|
+
if (!(e > 0)) return y("Amount must be greater than zero.");
|
|
385
|
+
d.mutate({
|
|
386
|
+
trustAccountId: n,
|
|
387
|
+
clientId: m.clientId,
|
|
388
|
+
matterId: m.matterId || null,
|
|
389
|
+
amount: e,
|
|
390
|
+
reference: m.reference.trim() || null,
|
|
391
|
+
description: m.description.trim() || null
|
|
392
|
+
}, {
|
|
393
|
+
onSuccess: () => {
|
|
394
|
+
s(), o();
|
|
395
|
+
},
|
|
396
|
+
onError: (e) => y(e instanceof Error ? e.message : "Failed.")
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
disabled: d.isPending,
|
|
400
|
+
children: d.isPending ? "Saving…" : t === "deposit" ? "Record deposit" : "Record withdrawal"
|
|
401
|
+
})] }),
|
|
402
|
+
children: /* @__PURE__ */ z("div", {
|
|
403
|
+
className: "space-y-3",
|
|
404
|
+
children: [
|
|
405
|
+
v && /* @__PURE__ */ R(u, {
|
|
406
|
+
tone: "danger",
|
|
407
|
+
children: v
|
|
408
|
+
}),
|
|
409
|
+
t === "withdrawal" && /* @__PURE__ */ R(u, {
|
|
410
|
+
tone: "info",
|
|
411
|
+
children: "A withdrawal that would take this client’s trust balance below zero is rejected — trust funds are never allowed to go negative."
|
|
412
|
+
}),
|
|
413
|
+
/* @__PURE__ */ R(p, {
|
|
414
|
+
label: "Client",
|
|
415
|
+
required: !0,
|
|
416
|
+
children: /* @__PURE__ */ z(_, {
|
|
417
|
+
value: m.clientId,
|
|
418
|
+
onChange: b("clientId"),
|
|
419
|
+
children: [/* @__PURE__ */ R("option", {
|
|
420
|
+
value: "",
|
|
421
|
+
children: "Select…"
|
|
422
|
+
}), f.data?.items.map((e) => /* @__PURE__ */ R("option", {
|
|
423
|
+
value: e.id,
|
|
424
|
+
children: e.name
|
|
425
|
+
}, e.id))]
|
|
426
|
+
})
|
|
427
|
+
}),
|
|
428
|
+
/* @__PURE__ */ z("div", {
|
|
429
|
+
className: "grid grid-cols-2 gap-3",
|
|
430
|
+
children: [/* @__PURE__ */ R(p, {
|
|
431
|
+
label: "Amount",
|
|
432
|
+
required: !0,
|
|
433
|
+
children: /* @__PURE__ */ R(w, {
|
|
434
|
+
type: "number",
|
|
435
|
+
min: "0.01",
|
|
436
|
+
step: "0.01",
|
|
437
|
+
value: m.amount,
|
|
438
|
+
onChange: b("amount")
|
|
439
|
+
})
|
|
440
|
+
}), /* @__PURE__ */ R(p, {
|
|
441
|
+
label: "Reference (optional)",
|
|
442
|
+
children: /* @__PURE__ */ R(w, {
|
|
443
|
+
value: m.reference,
|
|
444
|
+
onChange: b("reference"),
|
|
445
|
+
placeholder: "CHK-1042"
|
|
446
|
+
})
|
|
447
|
+
})]
|
|
448
|
+
}),
|
|
449
|
+
m.amount && /* @__PURE__ */ R("div", {
|
|
450
|
+
className: "rounded-lg bg-bg-sunken px-3 py-2 text-sm text-text-secondary",
|
|
451
|
+
children: D(Number(m.amount) || 0, r)
|
|
452
|
+
}),
|
|
453
|
+
/* @__PURE__ */ R(p, {
|
|
454
|
+
label: "Description (optional)",
|
|
455
|
+
children: /* @__PURE__ */ R(C, {
|
|
456
|
+
value: m.description,
|
|
457
|
+
onChange: b("description")
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
]
|
|
461
|
+
})
|
|
462
|
+
});
|
|
463
|
+
}, W = ({ trustAccountId: t, onClose: r, onSaved: i }) => {
|
|
464
|
+
let a = n(), o = e({ pagination: { limit: 100 } }), [s, c] = k({
|
|
465
|
+
clientId: "",
|
|
466
|
+
amount: "",
|
|
467
|
+
reason: ""
|
|
468
|
+
}), [l, d] = k(null), f = (e) => (t) => c((n) => ({
|
|
469
|
+
...n,
|
|
470
|
+
[e]: t.target.value
|
|
471
|
+
}));
|
|
472
|
+
return /* @__PURE__ */ R(g, {
|
|
473
|
+
open: !0,
|
|
474
|
+
onClose: r,
|
|
475
|
+
title: "Transfer to operating",
|
|
476
|
+
footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
|
|
477
|
+
variant: "outline",
|
|
478
|
+
onClick: r,
|
|
479
|
+
disabled: a.isPending,
|
|
480
|
+
children: "Cancel"
|
|
481
|
+
}), /* @__PURE__ */ R(B, {
|
|
482
|
+
onClick: () => {
|
|
483
|
+
if (d(null), !s.clientId) return d("Select a client.");
|
|
484
|
+
let e = Number(s.amount);
|
|
485
|
+
if (!(e > 0)) return d("Amount must be greater than zero.");
|
|
486
|
+
a.mutate({
|
|
487
|
+
trustAccountId: t,
|
|
488
|
+
clientId: s.clientId,
|
|
489
|
+
amount: e,
|
|
490
|
+
reason: s.reason.trim() || null
|
|
491
|
+
}, {
|
|
492
|
+
onSuccess: () => {
|
|
493
|
+
i(), r();
|
|
494
|
+
},
|
|
495
|
+
onError: (e) => d(e instanceof Error ? e.message : "Failed.")
|
|
496
|
+
});
|
|
497
|
+
},
|
|
498
|
+
disabled: a.isPending,
|
|
499
|
+
children: a.isPending ? "Transferring…" : "Transfer"
|
|
500
|
+
})] }),
|
|
501
|
+
children: /* @__PURE__ */ z("div", {
|
|
502
|
+
className: "space-y-3",
|
|
503
|
+
children: [
|
|
504
|
+
l && /* @__PURE__ */ R(u, {
|
|
505
|
+
tone: "danger",
|
|
506
|
+
children: l
|
|
507
|
+
}),
|
|
508
|
+
/* @__PURE__ */ R(u, {
|
|
509
|
+
tone: "info",
|
|
510
|
+
children: "Moves earned fees from this client’s trust ledger to the firm’s operating account. Rejected if it would take the client’s trust balance below zero."
|
|
511
|
+
}),
|
|
512
|
+
/* @__PURE__ */ R(p, {
|
|
513
|
+
label: "Client",
|
|
514
|
+
required: !0,
|
|
515
|
+
children: /* @__PURE__ */ z(_, {
|
|
516
|
+
value: s.clientId,
|
|
517
|
+
onChange: f("clientId"),
|
|
518
|
+
children: [/* @__PURE__ */ R("option", {
|
|
519
|
+
value: "",
|
|
520
|
+
children: "Select…"
|
|
521
|
+
}), o.data?.items.map((e) => /* @__PURE__ */ R("option", {
|
|
522
|
+
value: e.id,
|
|
523
|
+
children: e.name
|
|
524
|
+
}, e.id))]
|
|
525
|
+
})
|
|
526
|
+
}),
|
|
527
|
+
/* @__PURE__ */ R(p, {
|
|
528
|
+
label: "Amount",
|
|
529
|
+
required: !0,
|
|
530
|
+
children: /* @__PURE__ */ R(w, {
|
|
531
|
+
type: "number",
|
|
532
|
+
min: "0.01",
|
|
533
|
+
step: "0.01",
|
|
534
|
+
value: s.amount,
|
|
535
|
+
onChange: f("amount")
|
|
536
|
+
})
|
|
537
|
+
}),
|
|
538
|
+
/* @__PURE__ */ R(p, {
|
|
539
|
+
label: "Reason (optional)",
|
|
540
|
+
children: /* @__PURE__ */ R(C, {
|
|
541
|
+
value: s.reason,
|
|
542
|
+
onChange: f("reason"),
|
|
543
|
+
placeholder: "Invoice INV-2026-0001 paid from trust"
|
|
544
|
+
})
|
|
545
|
+
})
|
|
546
|
+
]
|
|
547
|
+
})
|
|
548
|
+
});
|
|
549
|
+
}, G = ({ trustAccountId: e, onClose: t, onSaved: n }) => {
|
|
550
|
+
let i = r(), [a, o] = k(""), [s, c] = k(null);
|
|
551
|
+
return /* @__PURE__ */ R(g, {
|
|
552
|
+
open: !0,
|
|
553
|
+
onClose: t,
|
|
554
|
+
title: "Reconcile trust account",
|
|
555
|
+
footer: /* @__PURE__ */ z(L, { children: [/* @__PURE__ */ R(B, {
|
|
556
|
+
variant: "outline",
|
|
557
|
+
onClick: t,
|
|
558
|
+
disabled: i.isPending,
|
|
559
|
+
children: "Cancel"
|
|
560
|
+
}), /* @__PURE__ */ R(B, {
|
|
561
|
+
onClick: () => {
|
|
562
|
+
c(null), i.mutate({
|
|
563
|
+
trustAccountId: e,
|
|
564
|
+
statementBalance: a.trim() ? Number(a) : null
|
|
565
|
+
}, {
|
|
566
|
+
onSuccess: (e) => {
|
|
567
|
+
n(e), t();
|
|
568
|
+
},
|
|
569
|
+
onError: (e) => c(e instanceof Error ? e.message : "Failed.")
|
|
570
|
+
});
|
|
571
|
+
},
|
|
572
|
+
disabled: i.isPending,
|
|
573
|
+
children: i.isPending ? "Reconciling…" : "Reconcile"
|
|
574
|
+
})] }),
|
|
575
|
+
children: /* @__PURE__ */ z("div", {
|
|
576
|
+
className: "space-y-3",
|
|
577
|
+
children: [
|
|
578
|
+
s && /* @__PURE__ */ R(u, {
|
|
579
|
+
tone: "danger",
|
|
580
|
+
children: s
|
|
581
|
+
}),
|
|
582
|
+
/* @__PURE__ */ R(u, {
|
|
583
|
+
tone: "info",
|
|
584
|
+
children: "Computes the book balance and every client’s subsidiary-ledger balance as of now, compares it against the bank statement balance below, and marks every not-yet-reconciled entry as reconciled."
|
|
585
|
+
}),
|
|
586
|
+
/* @__PURE__ */ R(p, {
|
|
587
|
+
label: "Bank statement balance (optional)",
|
|
588
|
+
children: /* @__PURE__ */ R(w, {
|
|
589
|
+
type: "number",
|
|
590
|
+
step: "0.01",
|
|
591
|
+
value: a,
|
|
592
|
+
onChange: (e) => o(e.target.value),
|
|
593
|
+
placeholder: "Leave blank to skip the bank-balance check"
|
|
594
|
+
})
|
|
595
|
+
})
|
|
596
|
+
]
|
|
597
|
+
})
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
//#endregion
|
|
601
|
+
export { V as TrustPage };
|
|
602
|
+
|
|
603
|
+
//# sourceMappingURL=TrustPage.js.map
|