[CLAUDE] Docs: User Manual 7 file rewrite compact cho end-user
User feedback: "ko cần quá đầy đủ chi tiết, cho end-user họ làm". Rewrite 7 generator scripts theo style end-user friendly: - Bỏ field validation table 5 cột (Tên field / Kiểu / Bắt buộc / Validation / Ví dụ) - Bỏ error troubleshoot table 3 cột (Lỗi / Nguyên nhân / Cách xử lý) - Bỏ FAQ chi tiết 8 câu (giữ 1 chương "Khi gặp lỗi" 4-5 bullet) - Bỏ phím tắt table - Giữ: tổng quan ngắn, numbered steps đơn giản, note/warn/tip chỉ khi critical 7 file generator (đã refactor dùng _helpers.js shared): - _gen-user-01: Bắt đầu (login + đổi pwd + hồ sơ + dashboard + sidebar + chuông) - _gen-user-02: Hợp đồng (7 loại + tạo + sửa + xóa + đính kèm + comment) - _gen-user-03: Duyệt Workflow (9 phase + 2-stage NV/TPB + reject + resume) - _gen-user-04: Phiếu Duyệt NCC (PE) (A/B + matrix báo giá + winner + 4PB + tạo HĐ) - _gen-user-05: Ngân sách (tạo + hạng mục + WF 3-step + liên kết HĐ/PE) - _gen-user-06: Cheatsheet 7 loại HĐ (mỗi loại 1 page: use case + field + format mã) - _gen-admin-02: Quản lý Users-Roles (tạo + role + reset + lock + bypass review S9) Setup: package.json + npm install docx@9.5.0 + script "gen:all". Output sizes: - 01: 12.1 KB (cũ 21.7 KB → giảm ~44%) - 02: 12.4 KB - 03: 12.2 KB - 04: 12.4 KB - 05: 12.0 KB - 06: 12.8 KB - admin-02: 12.7 KB - Tổng ~86 KB cho 7 file đầy đủ chức năng cốt lõi. Note: ContractType label "Phương án" → "Giải pháp" (đã rebrand session 3). 2-stage dept approval mention ở Phần 03 + admin-02 (Migration 16 Session 8/9). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
292
docs/_user-guide/_helpers.js
Normal file
292
docs/_user-guide/_helpers.js
Normal file
@ -0,0 +1,292 @@
|
||||
// Shared helpers cho 5 file user manual (02-06)
|
||||
const {
|
||||
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
||||
Header, Footer, AlignmentType, LevelFormat,
|
||||
HeadingLevel, BorderStyle, WidthType, ShadingType, PageNumber,
|
||||
TableOfContents, TabStopType, TabStopPosition,
|
||||
} = require('docx');
|
||||
|
||||
const PAGE_WIDTH = 12240, PAGE_HEIGHT = 15840, MARGIN = 1440;
|
||||
const CONTENT_WIDTH = PAGE_WIDTH - MARGIN * 2; // 9360
|
||||
const FONT = "Calibri";
|
||||
const BRAND = "1F7DC1";
|
||||
|
||||
// ===== Heading helpers =====
|
||||
const h1 = (text) => new Paragraph({
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
children: [new TextRun({ text, font: FONT, size: 32, bold: true, color: BRAND })],
|
||||
spacing: { before: 360, after: 200 },
|
||||
});
|
||||
const h2 = (text) => new Paragraph({
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
children: [new TextRun({ text, font: FONT, size: 26, bold: true, color: "333333" })],
|
||||
spacing: { before: 280, after: 160 },
|
||||
});
|
||||
const h3 = (text) => new Paragraph({
|
||||
heading: HeadingLevel.HEADING_3,
|
||||
children: [new TextRun({ text, font: FONT, size: 22, bold: true, color: "555555" })],
|
||||
spacing: { before: 200, after: 120 },
|
||||
});
|
||||
|
||||
// ===== Text helpers =====
|
||||
const p = (text, opts = {}) => new Paragraph({
|
||||
children: [new TextRun({ text, font: FONT, size: 22, ...(opts.run || {}) })],
|
||||
spacing: { after: 120 },
|
||||
...opts,
|
||||
});
|
||||
const bullet = (text, level = 0) => new Paragraph({
|
||||
numbering: { reference: "bullets", level },
|
||||
children: [new TextRun({ text, font: FONT, size: 22 })],
|
||||
spacing: { after: 80 },
|
||||
});
|
||||
const num = (text, level = 0) => new Paragraph({
|
||||
numbering: { reference: "numbers", level },
|
||||
children: [new TextRun({ text, font: FONT, size: 22 })],
|
||||
spacing: { after: 80 },
|
||||
});
|
||||
|
||||
// ===== Callout boxes =====
|
||||
const note = (text) => new Paragraph({
|
||||
shading: { fill: "FFF4D6", type: ShadingType.CLEAR },
|
||||
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "F0B429" } },
|
||||
children: [new TextRun({ text: "💡 " + text, font: FONT, size: 22, italics: true })],
|
||||
spacing: { before: 120, after: 120 },
|
||||
indent: { left: 240 },
|
||||
});
|
||||
const warn = (text) => new Paragraph({
|
||||
shading: { fill: "FFE5E5", type: ShadingType.CLEAR },
|
||||
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "D63031" } },
|
||||
children: [new TextRun({ text: "⚠️ " + text, font: FONT, size: 22, italics: true })],
|
||||
spacing: { before: 120, after: 120 },
|
||||
indent: { left: 240 },
|
||||
});
|
||||
const tip = (text) => new Paragraph({
|
||||
shading: { fill: "E8F5E9", type: ShadingType.CLEAR },
|
||||
border: { left: { style: BorderStyle.SINGLE, size: 18, color: "4CAF50" } },
|
||||
children: [new TextRun({ text: "✓ " + text, font: FONT, size: 22, italics: true })],
|
||||
spacing: { before: 120, after: 120 },
|
||||
indent: { left: 240 },
|
||||
});
|
||||
const code = (text) => new Paragraph({
|
||||
shading: { fill: "F5F5F5", type: ShadingType.CLEAR },
|
||||
children: [new TextRun({ text, font: "Consolas", size: 20 })],
|
||||
spacing: { before: 80, after: 120 },
|
||||
indent: { left: 240 },
|
||||
});
|
||||
|
||||
// ===== Mock screenshot placeholder =====
|
||||
const mockScreenshot = (label) => new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
shading: { fill: "FAFAFA", type: ShadingType.CLEAR },
|
||||
border: {
|
||||
top: { style: BorderStyle.DASHED, size: 6, color: "999999" },
|
||||
bottom: { style: BorderStyle.DASHED, size: 6, color: "999999" },
|
||||
left: { style: BorderStyle.DASHED, size: 6, color: "999999" },
|
||||
right: { style: BorderStyle.DASHED, size: 6, color: "999999" },
|
||||
},
|
||||
spacing: { before: 200, after: 200 },
|
||||
children: [new TextRun({
|
||||
text: `[ Ảnh chụp màn hình: ${label} ]`,
|
||||
font: FONT, size: 20, italics: true, color: "999999",
|
||||
})],
|
||||
});
|
||||
|
||||
// ===== Table cell builder =====
|
||||
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
|
||||
const borders = { top: border, bottom: border, left: border, right: border };
|
||||
const cellMargins = { top: 80, bottom: 80, left: 120, right: 120 };
|
||||
const cell = (text, opts = {}) => new TableCell({
|
||||
borders,
|
||||
width: { size: opts.width, type: WidthType.DXA },
|
||||
shading: opts.shading ? { fill: opts.shading, type: ShadingType.CLEAR } : undefined,
|
||||
margins: cellMargins,
|
||||
children: [new Paragraph({
|
||||
children: [new TextRun({
|
||||
text, font: FONT, size: 20,
|
||||
bold: opts.bold || false,
|
||||
color: opts.color || "000000",
|
||||
})],
|
||||
})],
|
||||
});
|
||||
|
||||
// ===== Bảng nhập liệu chuẩn 5 cột =====
|
||||
const fieldTable = (rows) => {
|
||||
const widths = [2200, 1500, 1100, 2860, 1700];
|
||||
return new Table({
|
||||
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
|
||||
columnWidths: widths,
|
||||
rows: [
|
||||
new TableRow({ tableHeader: true, children: [
|
||||
cell("Tên field", { width: widths[0], bold: true, shading: BRAND, color: "FFFFFF" }),
|
||||
cell("Kiểu dữ liệu", { width: widths[1], bold: true, shading: BRAND, color: "FFFFFF" }),
|
||||
cell("Bắt buộc", { width: widths[2], bold: true, shading: BRAND, color: "FFFFFF" }),
|
||||
cell("Quy tắc validation", { width: widths[3], bold: true, shading: BRAND, color: "FFFFFF" }),
|
||||
cell("Ví dụ", { width: widths[4], bold: true, shading: BRAND, color: "FFFFFF" }),
|
||||
]}),
|
||||
...rows.map(r => new TableRow({ children: [
|
||||
cell(r[0], { width: widths[0], bold: true }),
|
||||
cell(r[1], { width: widths[1] }),
|
||||
cell(r[2], { width: widths[2], color: r[2] === "Có" ? "D63031" : "999999", bold: r[2] === "Có" }),
|
||||
cell(r[3], { width: widths[3] }),
|
||||
cell(r[4], { width: widths[4] }),
|
||||
]})),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// ===== Bảng lỗi 3 cột =====
|
||||
const errorTable = (rows) => {
|
||||
const widths = [2800, 3360, 3200];
|
||||
return new Table({
|
||||
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
|
||||
columnWidths: widths,
|
||||
rows: [
|
||||
new TableRow({ tableHeader: true, children: [
|
||||
cell("Lỗi / Triệu chứng", { width: widths[0], bold: true, shading: "D63031", color: "FFFFFF" }),
|
||||
cell("Nguyên nhân", { width: widths[1], bold: true, shading: "D63031", color: "FFFFFF" }),
|
||||
cell("Cách xử lý", { width: widths[2], bold: true, shading: "D63031", color: "FFFFFF" }),
|
||||
]}),
|
||||
...rows.map(r => new TableRow({ children: [
|
||||
cell(r[0], { width: widths[0], bold: true }),
|
||||
cell(r[1], { width: widths[1] }),
|
||||
cell(r[2], { width: widths[2] }),
|
||||
]})),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// ===== Bảng tổng quát N cột =====
|
||||
const genericTable = (headers, rows, widths, headerColor = BRAND) => {
|
||||
return new Table({
|
||||
width: { size: CONTENT_WIDTH, type: WidthType.DXA },
|
||||
columnWidths: widths,
|
||||
rows: [
|
||||
new TableRow({ tableHeader: true, children: headers.map((h, i) =>
|
||||
cell(h, { width: widths[i], bold: true, shading: headerColor, color: "FFFFFF" })
|
||||
)}),
|
||||
...rows.map(r => new TableRow({ children: r.map((c, i) =>
|
||||
cell(typeof c === 'string' ? c : c.text, {
|
||||
width: widths[i],
|
||||
bold: typeof c === 'object' ? c.bold : (i === 0),
|
||||
})
|
||||
)})),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// ===== Title page =====
|
||||
const titlePage = (subtitle, partTitle) => [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { before: 1200, after: 300 },
|
||||
children: [new TextRun({ text: "SOLUTION_ERP", font: FONT, size: 56, bold: true, color: BRAND })],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 120 },
|
||||
children: [new TextRun({
|
||||
text: "Hướng dẫn Sử dụng - User Manual",
|
||||
font: FONT, size: 36, bold: true, color: "333333",
|
||||
})],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 600 },
|
||||
children: [new TextRun({
|
||||
text: partTitle,
|
||||
font: FONT, size: 28, color: "666666",
|
||||
})],
|
||||
}),
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { after: 1200 },
|
||||
children: [new TextRun({
|
||||
text: "Công ty TNHH Xây dựng Solutions | Hệ thống Quản lý Hợp đồng",
|
||||
font: FONT, size: 22, italics: true, color: "888888",
|
||||
})],
|
||||
}),
|
||||
new Paragraph({ children: [new TextRun("")], pageBreakBefore: true }),
|
||||
h1("Mục lục"),
|
||||
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }),
|
||||
new Paragraph({ children: [new TextRun("")], pageBreakBefore: true }),
|
||||
];
|
||||
|
||||
// ===== Document builder =====
|
||||
const buildDoc = (title, headerLabel, contentChildren) => new Document({
|
||||
creator: "SOLUTION_ERP",
|
||||
title,
|
||||
description: title,
|
||||
styles: {
|
||||
default: { document: { run: { font: FONT, size: 22 } } },
|
||||
paragraphStyles: [
|
||||
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 32, bold: true, font: FONT, color: BRAND },
|
||||
paragraph: { spacing: { before: 360, after: 200 }, outlineLevel: 0 } },
|
||||
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 26, bold: true, font: FONT, color: "333333" },
|
||||
paragraph: { spacing: { before: 280, after: 160 }, outlineLevel: 1 } },
|
||||
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 22, bold: true, font: FONT, color: "555555" },
|
||||
paragraph: { spacing: { before: 200, after: 120 }, outlineLevel: 2 } },
|
||||
],
|
||||
},
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: "bullets",
|
||||
levels: [
|
||||
{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
|
||||
{ level: 1, format: LevelFormat.BULLET, text: "◦", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 1440, hanging: 360 } } } },
|
||||
] },
|
||||
{ reference: "numbers",
|
||||
levels: [
|
||||
{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
|
||||
{ level: 1, format: LevelFormat.LOWER_LETTER, text: "%2)", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 1440, hanging: 360 } } } },
|
||||
] },
|
||||
],
|
||||
},
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
size: { width: PAGE_WIDTH, height: PAGE_HEIGHT },
|
||||
margin: { top: MARGIN, right: MARGIN, bottom: MARGIN, left: MARGIN },
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
default: new Header({ children: [new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [new TextRun({
|
||||
text: headerLabel,
|
||||
font: FONT, size: 18, color: "888888",
|
||||
})],
|
||||
})]}),
|
||||
},
|
||||
footers: {
|
||||
default: new Footer({ children: [new Paragraph({
|
||||
tabStops: [{ type: TabStopType.RIGHT, position: TabStopPosition.MAX }],
|
||||
children: [
|
||||
new TextRun({ text: "© Solutions | v1.0 | 2026-05-04", font: FONT, size: 18, color: "888888" }),
|
||||
new TextRun({ text: "\tTrang ", font: FONT, size: 18, color: "888888" }),
|
||||
new TextRun({ children: [PageNumber.CURRENT], font: FONT, size: 18, color: "888888" }),
|
||||
new TextRun({ text: " / ", font: FONT, size: 18, color: "888888" }),
|
||||
new TextRun({ children: [PageNumber.TOTAL_PAGES], font: FONT, size: 18, color: "888888" }),
|
||||
],
|
||||
})]}),
|
||||
},
|
||||
children: contentChildren,
|
||||
}],
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
// Constants
|
||||
PAGE_WIDTH, PAGE_HEIGHT, MARGIN, CONTENT_WIDTH, FONT, BRAND,
|
||||
// Helpers
|
||||
h1, h2, h3, p, bullet, num, note, warn, tip, code, mockScreenshot,
|
||||
cell, fieldTable, errorTable, genericTable,
|
||||
titlePage, buildDoc,
|
||||
// Re-export docx primitives
|
||||
Paragraph, TextRun, Packer,
|
||||
};
|
||||
Reference in New Issue
Block a user