import { Image, View } from "@react-pdf/renderer";
import type { ReactNode } from "react";
import { pinned } from "../../engine/columns";
import { CLOSING_ID, TOTALS_ID } from "../../engine/ids";
import { AvailableWidthProvider } from "../../blocks/AvailableWidth";
import { BottomSpacer } from "../../blocks/BottomSpacer";
import { FirstPageHeader } from "../../blocks/FirstPageHeader";
import { FitText } from "../../blocks/FitText";
import { IdText } from "../../blocks/IdText";
import { TableRows } from "../../blocks/ItemsTable";
import { Row } from "../../blocks/Row";
import { SafeTxt } from "../../blocks/SafeTxt";
import { Slot } from "../../blocks/slots";
import { TableGuard } from "../../blocks/TableGuard";
import { Txt } from "../../blocks/Txt";
import { AccentRule } from "../../decor/AccentRule";
import { META_ID, SUPPLIER_ID, TITLE_ID } from "../classic/ids";
import type { PlacedLogo, TemplateProps } from "../contract";
import { includesName } from "../logoName";
import { CANCELLED_INK, CANCELLED_SENTENCE } from "../neutral/plan";
import { atRate, LABEL_GAP, NOTES_FLOW_GAP, paymentRows, planMonolith, REVERSE_GAP, roleOf, signatureOptions, type MonolithPlan } from "./plan";
import type { MonolithVariant } from "./tokens";
/** Layout-tree ids of the blocks that are Monolith's own (the harness ids are in classic/ids.ts). */
export const BRAND_ID = "monolith-brand";
export const IDS_ID = "monolith-ids";
interface Ctx {
model: TemplateProps["model"];
plan: MonolithPlan;
v: MonolithVariant;
logo: PlacedLogo | null;
}
function Spacer({ h }: { h: number }) {
return h > 0 ? : null;
}
function Rule({ ctx, width }: { ctx: Ctx; width?: number }) {
return ;
}
// --- header -----------------------------------------------------------------------------------------------------
function Title({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const h = plan.head;
return (
{model.docTitle}
);
}
/** The logo (right-anchored in the stack's column) or the vendor name; under it the date and number lines flow, so a tall logo never covers them. */
function Brand({ ctx }: { ctx: Ctx }) {
const { model, plan, v, logo } = ctx;
const h = plan.head;
const name = h.showName ? (
{model.supplier.name}
) : (
);
const body = logo ? (
{name}
) : (
name
);
return (
{body}
);
}
function MetaStack({ ctx }: { ctx: Ctx }) {
const { plan, v } = ctx;
const h = plan.head;
return (
{h.lines.map((l, i) => (
{l.text}
))}
);
}
function HeaderRow({ ctx }: { ctx: Ctx }) {
const { plan } = ctx;
return (
);
}
// --- info row ---------------------------------------------------------------------------------------------------
function ClientBlock({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const r = v.roles;
const c = model.client;
const w = plan.info.clientW;
return (
Billed to:
{c.name}
{c.addressLines.map((line, i) => (
{line}
))}
{c.poNumber ? (
{`PO: ${c.poNumber}`}
) : null}
);
}
function IdsBlock({ ctx }: { ctx: Ctx }) {
const { model, v } = ctx;
const r = v.roles;
const s = model.supplier;
return (
Supplier:
{s.pan ? {`PAN: ${s.pan}`} : null}
);
}
// --- totals -----------------------------------------------------------------------------------------------------
function TotalRow({ ctx, label, amount, strong }: { ctx: Ctx; label: string; amount: string; strong?: boolean }) {
const { plan, v } = ctx;
const t = plan.tail;
const r = v.roles;
return (
{label}
{amount}
);
}
function TotalsBox({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const t = plan.tail;
return (
{t.stack.map((row, i) => (
))}
} />
);
}
function Tail({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const t = plan.tail;
const r = v.roles;
return (
{t.reverseH > 0 ? 0 ? REVERSE_GAP : 0 }} /> : null}
);
}
// --- closing ----------------------------------------------------------------------------------------------------
/** Notes: a label held with the first line, every line unbreakable (a cut Text would leave a zero-line node). */
function Notes({ ctx, width }: { ctx: Ctx; width: number }) {
const { plan, v } = ctx;
const r = v.roles;
const lines = plan.closing.notes;
if (lines.length === 0) return null;
return (
Notes / terms:
{lines[0]}
{lines.slice(1).map((line, i) => (
{line}
))}
);
}
function Payment({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const r = v.roles;
const cl = v.closing;
const w = plan.closing.leftW;
if (!model.bank) return ;
return (
Payment information :
{model.bank.accountName}
{paymentRows(model).map((x, i) => (
{x.label}
:
{x.value}
))}
);
}
function Vendor({ ctx }: { ctx: Ctx }) {
const { model, v } = ctx;
const r = v.roles;
const s = model.supplier;
return (
{s.name}
{s.addressLines.map((line, i) => (
{line}
))}
{s.phone ? (
{s.phone}
) : null}
{s.email ? (
{s.email}
) : null}
);
}
function Closing({ ctx }: { ctx: Ctx }) {
const { model, plan, v } = ctx;
const cl = v.closing;
const c = plan.closing;
const opts = signatureOptions(v);
return (
<>
{c.notesBeside ? : }
{model.cancelled ? (
{CANCELLED_SENTENCE}
) : null}
>
);
}
// --- the layout -------------------------------------------------------------------------------------------------
export function MonolithBody({ model, frame, logo, prefs, variant: v }: TemplateProps & { variant: MonolithVariant }) {
const plan = planMonolith(model, frame, v, logo, includesName({ model, prefs }));
const ctx: Ctx = { model, plan, v, logo };
const r = v.roles;
const notesFlow: ReactNode = plan.closing.notesBeside || plan.closing.notes.length === 0 ? null : (
);
return (
<>
{plan.info.hasIds ? (
) : (
)}
}
/>
{notesFlow}
>
);
}