feat: simplified document editor + optional blocks
Schema: - Block: new optional fields `optional` (boolean) and `optionalLabel` (string) - DocBody: new field `disabledBlockIds` — IDs of optional blocks turned off on this instance - Renderer skips blocks whose id is in disabledBlockIds Template authoring (BlocksEditor): - Each block card has «Опциональный» checkbox + custom label input - Optional blocks visually highlighted (amber border) Document editing (DocumentEdit): - New «Расширенный режим» toggle in header (default off → simple mode) - Simple mode: only document header, lines, and «Дополнительные пункты» section listing optional blocks as checkboxes - Advanced mode: also shows full BlocksEditor (previous behavior) - Toggling optional block updates body.disabledBlockIds; renderer/PDF reflects it Workflow: автор шаблона помечает спорные блоки как опциональные → пользователь при создании документа просто отмечает галочки, не залезая в контент. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,7 +36,15 @@ export const VAT_PERCENT: Record<VatRate, number> = {
|
||||
vat_20: 20,
|
||||
};
|
||||
|
||||
const blockBase = z.object({ id: z.string().min(1) });
|
||||
// Базовые поля для всех блоков:
|
||||
// - optional: блок помечается как «выбираемый при создании документа»;
|
||||
// автор шаблона ставит флаг, пользователь при инстансировании видит его как checkbox.
|
||||
// - optionalLabel: пользовательский лейбл для checkbox (если не задан — берётся стандартное имя типа).
|
||||
const blockBase = z.object({
|
||||
id: z.string().min(1),
|
||||
optional: z.boolean().optional(),
|
||||
optionalLabel: z.string().max(200).optional(),
|
||||
});
|
||||
|
||||
export const HeadingBlock = blockBase.extend({
|
||||
type: z.literal('heading'),
|
||||
@@ -109,7 +117,10 @@ export const DocBody = z.object({
|
||||
version: z.literal(1),
|
||||
blocks: z.array(Block),
|
||||
vars: z.record(z.unknown()).default({}),
|
||||
// ID блоков, которые на этом инстансе документа выключены пользователем (galочка снята).
|
||||
// Применимо только к блокам с optional=true; они физически остаются в body, но не попадают в рендер.
|
||||
disabledBlockIds: z.array(z.string()).default([]),
|
||||
});
|
||||
export type DocBody = z.infer<typeof DocBody>;
|
||||
|
||||
export const emptyDocBody = (): DocBody => ({ version: 1, blocks: [], vars: {} });
|
||||
export const emptyDocBody = (): DocBody => ({ version: 1, blocks: [], vars: {}, disabledBlockIds: [] });
|
||||
|
||||
@@ -288,7 +288,11 @@ export function renderDocumentHtml(
|
||||
rubInWords?: (cents: number) => string;
|
||||
} = {},
|
||||
): string {
|
||||
const blocksHtml = body.blocks.map((b) => renderBlock(b, ctx, opts.rubInWords)).join('\n');
|
||||
const disabled = new Set(body.disabledBlockIds ?? []);
|
||||
const blocksHtml = body.blocks
|
||||
.filter((b) => !disabled.has(b.id))
|
||||
.map((b) => renderBlock(b, ctx, opts.rubInWords))
|
||||
.join('\n');
|
||||
const title = opts.title ?? `Документ ${ctx.doc.number}`;
|
||||
|
||||
return `<!doctype html>
|
||||
|
||||
Reference in New Issue
Block a user