Form from template
This commit is contained in:
38
client/package-lock.json
generated
38
client/package-lock.json
generated
@ -42,8 +42,10 @@
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-templates": "^4.13.0",
|
||||
"easy-template-x": "^5.1.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"file-type": "^19.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.2",
|
||||
"ol": "^10.0.0",
|
||||
"ol-ext": "^4.0.23",
|
||||
@ -4257,6 +4259,15 @@
|
||||
"vite": "^4 || ^5"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zeit/schemas": {
|
||||
"version": "2.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
|
||||
@ -4643,7 +4654,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
@ -5560,7 +5570,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
@ -5963,6 +5972,18 @@
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/easy-template-x": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/easy-template-x/-/easy-template-x-5.1.0.tgz",
|
||||
"integrity": "sha512-vypMbIMLWLXoooA9rsL3SVN2oQtZwmmx1m4H8gi6JfbEXQQ5VLHGOUHYi9APbvN9R8Gx93r1fphdSFRHxozeYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "0.8.10",
|
||||
"json5": "2.2.3",
|
||||
"jszip": "3.10.1",
|
||||
"lodash.get": "4.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
@ -7150,7 +7171,7 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"optional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
@ -7851,7 +7872,6 @@
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@ -7884,6 +7904,7 @@
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
|
||||
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2",
|
||||
"atob": "^2.1.2",
|
||||
@ -8046,6 +8067,13 @@
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
|
||||
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@ -11280,7 +11308,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
@ -11801,7 +11828,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
|
@ -45,8 +45,10 @@
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"docx-templates": "^4.13.0",
|
||||
"easy-template-x": "^5.1.0",
|
||||
"embla-carousel-react": "^8.3.0",
|
||||
"file-type": "^19.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.2",
|
||||
"ol": "^10.0.0",
|
||||
"ol-ext": "^4.0.23",
|
||||
|
Binary file not shown.
@ -1,6 +1,11 @@
|
||||
import { Button, Flex } from "@mantine/core";
|
||||
import { useState } from "react";
|
||||
import createReport from 'docx-templates'
|
||||
import { ActionIcon, Button, Flex, Group, Input, Stack, Text, TextInput } from "@mantine/core"
|
||||
import { useEffect, useState } from "react"
|
||||
import createReport, { listCommands } from 'docx-templates'
|
||||
import { Dropzone, DropzoneProps, IMAGE_MIME_TYPE, MS_WORD_MIME_TYPE } from '@mantine/dropzone'
|
||||
import { IconFile, IconFileTypeDocx, IconPhoto, IconPlus, IconUpload, IconX } from "@tabler/icons-react"
|
||||
import { CommandSummary } from "docx-templates/lib/types"
|
||||
import { Control, Controller, FieldValues, SubmitHandler, useFieldArray, useForm, UseFormRegister } from "react-hook-form"
|
||||
import { TemplateHandler } from 'easy-template-x'
|
||||
|
||||
const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
@ -905,165 +910,403 @@ const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>`
|
||||
|
||||
const PrintReport = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleGenerateExcel = () => {
|
||||
// Define the example XML data
|
||||
const xmlData = `
|
||||
<root>
|
||||
<Kvp>
|
||||
<style_id>1</style_id>
|
||||
<region>Region 1</region>
|
||||
<city>City 1</city>
|
||||
<house_count>10</house_count>
|
||||
<square>1000</square>
|
||||
<people_count>500</people_count>
|
||||
<volume_heat>200</volume_heat>
|
||||
<sum_heat>1000</sum_heat>
|
||||
<volume_hwater>300</volume_hwater>
|
||||
<sum_hwater>1500</sum_hwater>
|
||||
<volume_cwater>400</volume_cwater>
|
||||
<sum_cwater>2000</sum_cwater>
|
||||
<volume_sewers>500</volume_sewers>
|
||||
<sum_sewers>2500</sum_sewers>
|
||||
<saldo_out>300</saldo_out>
|
||||
</Kvp>
|
||||
</root>
|
||||
`
|
||||
|
||||
const generateDocx = async () => {
|
||||
setLoading(true);
|
||||
// Parse the XSL template and XML data
|
||||
const parser = new DOMParser()
|
||||
const xslDoc = parser.parseFromString(xslTemplate, "application/xml")
|
||||
const xmlDoc = parser.parseFromString(xmlData, "application/xml")
|
||||
|
||||
// Apply the transformation
|
||||
const xsltProcessor = new XSLTProcessor()
|
||||
xsltProcessor.importStylesheet(xslDoc)
|
||||
const resultDocument = xsltProcessor.transformToDocument(xmlDoc)
|
||||
|
||||
// Serialize the result to a string
|
||||
const serializer = new XMLSerializer()
|
||||
const resultXml = serializer.serializeToString(resultDocument)
|
||||
|
||||
// Add missing Excel-specific headers if needed
|
||||
const correctedXml = `<?xml version="1.0" encoding="utf-8"?>\n` + resultXml
|
||||
|
||||
// Convert to Blob and trigger download
|
||||
const blob = new Blob([correctedXml], { type: "application/vnd.ms-excel" })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement("a")
|
||||
link.href = url
|
||||
link.download = "template.xls"
|
||||
link.click()
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const TemplateFormET = ({
|
||||
templateUrl
|
||||
}: {
|
||||
templateUrl: string
|
||||
}) => {
|
||||
const [templateUint8Array, setTemplateUint8Array] = useState<Uint8Array | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const loadTemplate = async (templateUrl: string) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch(templateUrl)
|
||||
const templateArrayBuffer = await response.arrayBuffer()
|
||||
|
||||
setTemplateUint8Array(new Uint8Array(templateArrayBuffer))
|
||||
} catch (error) {
|
||||
console.error("Error generating DOCX:", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loadTags = async (templateUint8Array: Uint8Array) => {
|
||||
const handler = new TemplateHandler()
|
||||
const tags = await handler.parseTags(templateUint8Array)
|
||||
|
||||
console.log(tags)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUint8Array) {
|
||||
loadTags(templateUint8Array)
|
||||
}
|
||||
}, [templateUint8Array])
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUrl) {
|
||||
loadTemplate(templateUrl)
|
||||
}
|
||||
}, [templateUrl])
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface TemplateCommand extends CommandSummary {
|
||||
children?: CommandSummary[]
|
||||
}
|
||||
|
||||
export function parseCommandList(commands: CommandSummary[]): TemplateCommand[] {
|
||||
function parseBlock(startIndex: number, currentElement?: string): [TemplateCommand[], number] {
|
||||
const block: TemplateCommand[] = []
|
||||
let i = startIndex
|
||||
|
||||
while (i < commands.length) {
|
||||
const command = commands[i]
|
||||
|
||||
if (command.type === "FOR") {
|
||||
const [elementName, , arrayName] = command.code.split(" ")
|
||||
const forCommand: TemplateCommand = {
|
||||
raw: command.raw,
|
||||
type: command.type,
|
||||
code: arrayName,
|
||||
children: [],
|
||||
}
|
||||
|
||||
const [children, nextIndex] = parseBlock(i + 1, elementName)
|
||||
forCommand.children = children
|
||||
i = nextIndex
|
||||
block.push(forCommand)
|
||||
} else if (command.type === "END-FOR") {
|
||||
return [block, i + 1]
|
||||
} else {
|
||||
let code = command.code
|
||||
if (currentElement && (code.startsWith(`${currentElement}.`) || code.startsWith(`$${currentElement}.`))) {
|
||||
code = code.replace(`$${currentElement}.`, "").replace(`${currentElement}.`, "")
|
||||
}
|
||||
|
||||
block.push({
|
||||
...command,
|
||||
code,
|
||||
})
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return [block, i]
|
||||
}
|
||||
|
||||
const [parsed] = parseBlock(0)
|
||||
return parsed
|
||||
}
|
||||
|
||||
const FormLoop = ({
|
||||
control,
|
||||
register,
|
||||
command,
|
||||
}: {
|
||||
control: Control<FieldValues, any>,
|
||||
register: UseFormRegister<FieldValues>,
|
||||
command: TemplateCommand,
|
||||
}) => {
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: command.code,
|
||||
control
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack align="center">
|
||||
<Stack w='100%' key={command.code}>
|
||||
{
|
||||
fields.map((field, index) => (
|
||||
<Flex w='100%' justify='space-between' align='flex-end' key={field.id}>
|
||||
{command.children &&
|
||||
command.children.map(c =>
|
||||
renderCommand(
|
||||
control,
|
||||
register,
|
||||
c,
|
||||
`${c.code}`,
|
||||
`${command.code}.${index}.${c.code}`,
|
||||
`${command.code}.${index}.${c.code}`
|
||||
)
|
||||
)}
|
||||
<Button variant='subtle' onClick={() => {
|
||||
remove(index)
|
||||
}}>
|
||||
<IconX />
|
||||
</Button>
|
||||
</Flex>
|
||||
))
|
||||
}
|
||||
</Stack>
|
||||
<ActionIcon onClick={() => {
|
||||
if (command.children) {
|
||||
append(command.children.map(c => c.code).reduce((acc, key) => {
|
||||
acc[key] = '';
|
||||
return acc;
|
||||
}, {} as Record<string, string>))
|
||||
}
|
||||
}}>
|
||||
<IconPlus />
|
||||
</ActionIcon>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
const renderCommand = (
|
||||
control: Control<FieldValues, any>,
|
||||
register: UseFormRegister<FieldValues>,
|
||||
command: CommandSummary,
|
||||
label: string,
|
||||
key: string,
|
||||
name: string,
|
||||
) => {
|
||||
if (command.type === 'INS') {
|
||||
return (
|
||||
<TextInput
|
||||
label={label}
|
||||
key={key}
|
||||
{...register(name)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (command.type === 'IMAGE') {
|
||||
return (
|
||||
<Controller
|
||||
key={key}
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<Dropzone
|
||||
accept={IMAGE_MIME_TYPE}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
onReject={(files) => console.log('rejected files', files)}
|
||||
onDrop={(files) => {
|
||||
console.log(files[0])
|
||||
files[0].arrayBuffer().then(res => {
|
||||
onChange({
|
||||
width: 6,
|
||||
height: 6,
|
||||
data: new Uint8Array(res),
|
||||
extension: files[0]?.path?.match(/\.[^.]+$/)?.[0] || ""
|
||||
})
|
||||
})
|
||||
}}
|
||||
maxFiles={1}
|
||||
>
|
||||
<Group justify="center" gap="xl" mih={220} style={{ pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconFileTypeDocx size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
|
||||
<div>
|
||||
<Text size="xl" inline>
|
||||
Drag files here or click to select files
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" inline mt={7}>
|
||||
Attach as many files as you like, each file should not exceed 5mb
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Dropzone>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const TemplateForm = ({
|
||||
templateUrl,
|
||||
}: {
|
||||
templateUrl: string,
|
||||
}) => {
|
||||
const { register, control, handleSubmit, reset, watch, formState } = useForm({
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const [templateUint8Array, setTemplateUint8Array] = useState<Uint8Array | null>(null)
|
||||
|
||||
const [commandList, setCommandList] = useState<TemplateCommand[]>([])
|
||||
|
||||
const saveTest = async (templateUint8Array: Uint8Array, data: any) => {
|
||||
setSaving(true)
|
||||
|
||||
try {
|
||||
// Fetch the DOCX template from the public folder
|
||||
const response = await fetch("/template.docx");
|
||||
const response_table = await fetch("/template_table.docx");
|
||||
const templateArrayBuffer = await response.arrayBuffer();
|
||||
const templateArrayBuffer_table = await response_table.arrayBuffer();
|
||||
|
||||
// Convert ArrayBuffer to Uint8Array (Fix TypeScript error)
|
||||
const templateUint8Array = new Uint8Array(templateArrayBuffer);
|
||||
const templateUint8Array_table = new Uint8Array(templateArrayBuffer_table);
|
||||
|
||||
// Fetch the image (Example: Load from public folder)
|
||||
const imageResponse = await fetch("/test.png"); // Change this to your image path
|
||||
const imageBlob = await imageResponse.blob();
|
||||
const imageArrayBuffer = await imageBlob.arrayBuffer();
|
||||
const imageUint8Array = new Uint8Array(imageArrayBuffer);
|
||||
|
||||
// Generate the DOCX file with the replacement
|
||||
const report = await createReport({
|
||||
template: templateUint8Array, // Ensure it's Uint8Array
|
||||
data: {
|
||||
test: "Hello World",
|
||||
myImage: {
|
||||
width: 6, // Width in cm
|
||||
height: 6, // Height in cm
|
||||
data: imageUint8Array, // Image binary data
|
||||
extension: ".png", // Specify the image format
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const report_table = await createReport({
|
||||
template: templateUint8Array_table, // Ensure it's Uint8Array
|
||||
data: {
|
||||
test: "Hello World",
|
||||
rows: [
|
||||
{
|
||||
first: 'A',
|
||||
second: 'B',
|
||||
third: 'C',
|
||||
fourth: 'D',
|
||||
},
|
||||
{
|
||||
first: 'E',
|
||||
second: 'F',
|
||||
third: 'G',
|
||||
fourth: 'H',
|
||||
},
|
||||
{
|
||||
first: 'I',
|
||||
second: 'J',
|
||||
third: 'K',
|
||||
fourth: 'L',
|
||||
}
|
||||
]
|
||||
},
|
||||
});
|
||||
data: data,
|
||||
})
|
||||
|
||||
// Convert Uint8Array to a Blob
|
||||
const blob = new Blob([report], {
|
||||
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
});
|
||||
|
||||
const blob_table = new Blob([report_table], {
|
||||
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
});
|
||||
})
|
||||
|
||||
// Create a download link and trigger the download
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "report.docx";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = "report.docx"
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
|
||||
// Create a download link and trigger the download
|
||||
const url_table = URL.createObjectURL(blob_table);
|
||||
const a_table = document.createElement("a");
|
||||
a_table.href = url_table;
|
||||
a_table.download = "report_table.docx";
|
||||
document.body.appendChild(a_table);
|
||||
a_table.click();
|
||||
document.body.removeChild(a_table);
|
||||
|
||||
// Revoke the object URL after download
|
||||
URL.revokeObjectURL(url);
|
||||
URL.revokeObjectURL(url_table);
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
console.error("Error generating DOCX:", error);
|
||||
console.error("Error generating DOCX:", error)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateExcel = () => {
|
||||
// Define the example XML data
|
||||
const xmlData = `
|
||||
<root>
|
||||
<Kvp>
|
||||
<style_id>1</style_id>
|
||||
<region>Region 1</region>
|
||||
<city>City 1</city>
|
||||
<house_count>10</house_count>
|
||||
<square>1000</square>
|
||||
<people_count>500</people_count>
|
||||
<volume_heat>200</volume_heat>
|
||||
<sum_heat>1000</sum_heat>
|
||||
<volume_hwater>300</volume_hwater>
|
||||
<sum_hwater>1500</sum_hwater>
|
||||
<volume_cwater>400</volume_cwater>
|
||||
<sum_cwater>2000</sum_cwater>
|
||||
<volume_sewers>500</volume_sewers>
|
||||
<sum_sewers>2500</sum_sewers>
|
||||
<saldo_out>300</saldo_out>
|
||||
</Kvp>
|
||||
</root>
|
||||
`;
|
||||
const loadTemplate = async () => {
|
||||
setLoading(true)
|
||||
|
||||
// Parse the XSL template and XML data
|
||||
const parser = new DOMParser();
|
||||
const xslDoc = parser.parseFromString(xslTemplate, "application/xml");
|
||||
const xmlDoc = parser.parseFromString(xmlData, "application/xml");
|
||||
try {
|
||||
const response = await fetch(templateUrl)
|
||||
const templateArrayBuffer = await response.arrayBuffer()
|
||||
|
||||
// Apply the transformation
|
||||
const xsltProcessor = new XSLTProcessor();
|
||||
xsltProcessor.importStylesheet(xslDoc);
|
||||
const resultDocument = xsltProcessor.transformToDocument(xmlDoc);
|
||||
|
||||
// Serialize the result to a string
|
||||
const serializer = new XMLSerializer();
|
||||
const resultXml = serializer.serializeToString(resultDocument);
|
||||
|
||||
// Add missing Excel-specific headers if needed
|
||||
const correctedXml = `<?xml version="1.0" encoding="utf-8"?>\n` + resultXml
|
||||
|
||||
// Convert to Blob and trigger download
|
||||
const blob = new Blob([correctedXml], { type: "application/vnd.ms-excel" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = "template.xls";
|
||||
link.click();
|
||||
|
||||
// Clean up
|
||||
URL.revokeObjectURL(url);
|
||||
setTemplateUint8Array(new Uint8Array(templateArrayBuffer))
|
||||
} catch (error) {
|
||||
console.error("Error generating DOCX:", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loadCommands = async (templateUint8Array: Uint8Array) => {
|
||||
try {
|
||||
await listCommands(templateUint8Array).then(l => {
|
||||
setCommandList(parseCommandList(l))
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error loading commands from DOCX:", error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUint8Array) {
|
||||
loadCommands(templateUint8Array)
|
||||
}
|
||||
}, [templateUint8Array])
|
||||
|
||||
const onSubmit: SubmitHandler<any> = async (data) => {
|
||||
try {
|
||||
if (templateUint8Array && data) {
|
||||
saveTest(templateUint8Array, data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (templateUrl) {
|
||||
loadTemplate()
|
||||
}
|
||||
}, [templateUrl])
|
||||
|
||||
if (commandList) {
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack>
|
||||
{commandList.map(command => {
|
||||
if (command.type === 'FOR') {
|
||||
return (
|
||||
<FormLoop key={command.code} control={control} register={register} command={command} />
|
||||
)
|
||||
} else {
|
||||
return renderCommand(control, register, command, command.code, command.code, command.code)
|
||||
}
|
||||
})}
|
||||
<Button type='submit'>Submit</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const PrintReport = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
return (
|
||||
<Flex p='sm' gap='sm'>
|
||||
<Button onClick={generateDocx} disabled={loading}>{loading ? "Генерация отчета..." : "Сохранить в docx"}</Button>
|
||||
<Button onClick={handleGenerateExcel}>Сохранить в Excel</Button>
|
||||
</Flex>
|
||||
<Stack p='sm' gap='sm' w='100%'>
|
||||
<TemplateForm templateUrl="/template_table.docx" />
|
||||
|
||||
<Flex gap='sm'>
|
||||
<Button onClick={handleGenerateExcel}>Сохранить в Excel</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1869,6 +1869,11 @@
|
||||
dependencies:
|
||||
"@swc/core" "^1.5.7"
|
||||
|
||||
"@xmldom/xmldom@0.8.10":
|
||||
version "0.8.10"
|
||||
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz"
|
||||
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
|
||||
|
||||
"@zeit/schemas@2.36.0":
|
||||
version "2.36.0"
|
||||
resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz"
|
||||
@ -2932,6 +2937,16 @@ eastasianwidth@^0.2.0:
|
||||
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
easy-template-x@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/easy-template-x/-/easy-template-x-5.1.0.tgz"
|
||||
integrity sha512-vypMbIMLWLXoooA9rsL3SVN2oQtZwmmx1m4H8gi6JfbEXQQ5VLHGOUHYi9APbvN9R8Gx93r1fphdSFRHxozeYw==
|
||||
dependencies:
|
||||
"@xmldom/xmldom" "0.8.10"
|
||||
json5 "2.2.3"
|
||||
jszip "3.10.1"
|
||||
lodash.get "4.4.2"
|
||||
|
||||
ejs@^3.1.6:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz"
|
||||
@ -3665,7 +3680,7 @@ hmac-drbg@^1.0.1:
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
html2canvas@^1.0.0-rc.5:
|
||||
html2canvas@^1.0.0-rc.5, html2canvas@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz"
|
||||
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
|
||||
@ -4056,7 +4071,7 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
|
||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||
|
||||
json5@^2.2.0, json5@^2.2.3:
|
||||
json5@^2.2.0, json5@^2.2.3, json5@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
@ -4090,7 +4105,7 @@ jspdf@^2.5.1, jspdf@^2.5.2:
|
||||
dompurify "^2.5.4"
|
||||
html2canvas "^1.0.0-rc.5"
|
||||
|
||||
jszip@^3.10.1:
|
||||
jszip@^3.10.1, jszip@3.10.1:
|
||||
version "3.10.1"
|
||||
resolved "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz"
|
||||
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
|
||||
@ -4176,6 +4191,11 @@ lodash.debounce@^4.0.8:
|
||||
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
|
||||
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
|
||||
lodash.get@4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz"
|
||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
|
Reference in New Issue
Block a user