@@ -216,9 +296,9 @@ function generatePayslipForm(data) {
${data.accountNumber ? `
+
Лицевой счет:
- ${data.accountNumber}
+ ${data.accountNumber}
` : ''}
diff --git a/backend/uploads/food/1793224182340 b/backend/uploads/food/1793224182340
new file mode 100644
index 0000000..57d3140
Binary files /dev/null and b/backend/uploads/food/1793224182340 differ
diff --git a/backend/uploads/food/1793224213479 b/backend/uploads/food/1793224213479
new file mode 100644
index 0000000..510928c
Binary files /dev/null and b/backend/uploads/food/1793224213479 differ
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b84a8d0..98e8352 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -18,14 +18,15 @@
"dompurify": "^3.4.13",
"dotenv": "^17.4.2",
"electron-is-dev": "^2.0.0",
- "framer-motion": "^13.1.0",
+ "framer-motion": "^13.1.1",
"pdf-lib": "^1.17.1",
"pdf-to-printer": "^5.8.1",
"pdfjs-dist": "^3.11.174",
"pdfkit": "^0.20.2",
+ "pdfmake": "^0.1.72",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "react-icons": "^4.11.0",
+ "react-icons": "^4.12.0",
"react-pdf": "^8.0.0",
"react-zoom-pan-pinch": "^3.3.0",
"sharp": "^0.35.4",
@@ -3060,6 +3061,22 @@
"dequal": "^2.0.3"
}
},
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/asn1js": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
@@ -3118,6 +3135,21 @@
"node": ">= 4.0.0"
}
},
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/aws4": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
@@ -3230,6 +3262,15 @@
"base64-js": "^1.1.2"
}
},
+ "node_modules/browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "~1.0.5"
+ }
+ },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -3646,6 +3687,24 @@
"node": ">=8"
}
},
+ "node_modules/call-bind": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
+ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "get-intrinsic": "^1.3.0",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -3659,6 +3718,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/canvas": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
@@ -4040,6 +4115,13 @@
"node": ">= 8"
}
},
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "deprecated": "Active development of CryptoJS has been discontinued. This library is no longer maintained.",
+ "license": "MIT"
+ },
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -4124,6 +4206,44 @@
"node": ">=6"
}
},
+ "node_modules/deep-equal": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz",
+ "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==",
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "call-bind": "^1.0.5",
+ "es-get-iterator": "^1.1.3",
+ "get-intrinsic": "^1.2.2",
+ "is-arguments": "^1.1.1",
+ "is-array-buffer": "^3.0.2",
+ "is-date-object": "^1.0.5",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "isarray": "^2.0.5",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.5.1",
+ "side-channel": "^1.0.4",
+ "which-boxed-primitive": "^1.0.2",
+ "which-collection": "^1.0.1",
+ "which-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/deep-equal/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -4151,9 +4271,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
- "dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@@ -4170,9 +4288,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
- "dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
@@ -5016,6 +5132,32 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-get-iterator": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz",
+ "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "is-arguments": "^1.1.1",
+ "is-map": "^2.0.2",
+ "is-set": "^2.0.2",
+ "is-string": "^1.0.7",
+ "isarray": "^2.0.5",
+ "stop-iteration-iterator": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-get-iterator/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -5285,6 +5427,21 @@
"node": ">=0.8"
}
},
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
@@ -5311,12 +5468,12 @@
}
},
"node_modules/framer-motion": {
- "version": "13.1.0",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.1.0.tgz",
- "integrity": "sha512-QSZrF0Id3QGuHJ+OL+9PSY9pk86C8ERFalwAGSchzTm65+ZoGH/RM26lmEARLljcHj2lqhv0jZOOks+EI3COOw==",
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.1.1.tgz",
+ "integrity": "sha512-B/xn2TPS4f61cEBLFjiYlQFnBZUW1YVj/LM+C+N4OP8Rs95VLEI2ot/RlfBg111la/EiyECFaJJi/A3FWA8MUA==",
"license": "MIT",
"dependencies": {
- "motion-dom": "^13.0.0",
+ "motion-dom": "^13.1.1",
"motion-utils": "^13.0.0",
"tslib": "^2.4.0"
},
@@ -5405,6 +5562,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gauge": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
@@ -5638,6 +5804,18 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -5652,9 +5830,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
- "dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@@ -5796,7 +5972,6 @@
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
- "optional": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
@@ -5870,6 +6045,20 @@
"devOptional": true,
"license": "ISC"
},
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ip-address": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
@@ -5880,6 +6069,98 @@
"node": ">= 12"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
+ "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -5907,6 +6188,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -5917,6 +6210,100 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -5930,6 +6317,34 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -7123,9 +7538,9 @@
}
},
"node_modules/motion-dom": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.0.0.tgz",
- "integrity": "sha512-Xk+SJas70uMAUIApg+m3lZDShxI3LBFHq7mFGbBKoRXc2PVPDyAKmzN64Bbzt4CZdP/CItTiJxWtn4TA0v53Ng==",
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-13.1.1.tgz",
+ "integrity": "sha512-XSf8VYWSB6G/0IY3rWVbyLcxWXtAVHkN1PQE2agTaCv3u8RGvbwu56TyyR/MNzBqqNavEBTZzErcxI1TxBrjcA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^13.0.0"
@@ -7395,17 +7810,63 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-is": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
+ "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true,
"license": "MIT",
- "optional": true,
"engines": {
"node": ">= 0.4"
}
},
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -7614,6 +8075,83 @@
"url": "https://paulmillr.com/funding/"
}
},
+ "node_modules/pdfmake": {
+ "version": "0.1.72",
+ "resolved": "https://registry.npmjs.org/pdfmake/-/pdfmake-0.1.72.tgz",
+ "integrity": "sha512-xZrPS+Safjf1I8ZYtMoXX83E6C6Pd1zFwa168yNTeeJWHclqf1z9DoYajjlY2uviN7gGyxwVZeou39uSk1oh1g==",
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "^0.6.2",
+ "linebreak": "^1.0.2",
+ "pdfkit": "^0.12.0",
+ "svg-to-pdfkit": "^0.1.8",
+ "xmldoc": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pdfmake/node_modules/@swc/helpers": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz",
+ "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/pdfmake/node_modules/clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/pdfmake/node_modules/fontkit": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz",
+ "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.3.13",
+ "brotli": "^1.3.2",
+ "clone": "^2.1.2",
+ "deep-equal": "^2.0.5",
+ "dfa": "^1.2.0",
+ "restructure": "^2.0.1",
+ "tiny-inflate": "^1.0.3",
+ "unicode-properties": "^1.3.1",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/pdfmake/node_modules/pdfkit": {
+ "version": "0.12.3",
+ "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.12.3.tgz",
+ "integrity": "sha512-+qDLgm2yq6WOKcxTb43lDeo3EtMIDQs0CK1RNqhHC9iT6u0KOmgwAClkYh9xFw2ATbmUZzt4f7KMwDCOfPDluA==",
+ "license": "MIT",
+ "dependencies": {
+ "crypto-js": "^4.0.0",
+ "fontkit": "^1.8.1",
+ "linebreak": "^1.0.2",
+ "png-js": "^1.0.0"
+ }
+ },
+ "node_modules/pdfmake/node_modules/png-js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz",
+ "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==",
+ "dependencies": {
+ "browserify-zlib": "^0.2.0"
+ }
+ },
+ "node_modules/pdfmake/node_modules/restructure": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz",
+ "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==",
+ "license": "MIT"
+ },
"node_modules/pe-library": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
@@ -7709,6 +8247,15 @@
"fflate": "^0.8.2"
}
},
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
@@ -8030,6 +8577,26 @@
"node": ">=8"
}
},
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -8262,12 +8829,28 @@
],
"license": "MIT"
},
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT",
- "optional": true
+ "license": "MIT"
},
"node_modules/sanitize-filename": {
"version": "1.6.4",
@@ -8283,7 +8866,6 @@
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
- "dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
@@ -8337,6 +8919,38 @@
"devOptional": true,
"license": "ISC"
},
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/sharp": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
@@ -8434,6 +9048,78 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -8727,6 +9413,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -8806,6 +9505,15 @@
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
+ "node_modules/svg-to-pdfkit": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/svg-to-pdfkit/-/svg-to-pdfkit-0.1.8.tgz",
+ "integrity": "sha512-QItiGZBy5TstGy+q8mjQTMGRlDDOARXLxH+sgVm1n/LYeo0zFcQlcCh8m4zi8QxctrxB9Kue/lStc/RD5iLadQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pdfkit": ">=0.8.1"
+ }
+ },
"node_modules/tar": {
"version": "7.5.22",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
@@ -10549,6 +11257,64 @@
"node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.22",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
+ "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@@ -10650,6 +11416,15 @@
"node": ">=8.0"
}
},
+ "node_modules/xmldoc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz",
+ "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": "^1.2.4"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index e5e961e..005e56a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -14,14 +14,15 @@
"dompurify": "^3.4.13",
"dotenv": "^17.4.2",
"electron-is-dev": "^2.0.0",
- "framer-motion": "^13.1.0",
+ "framer-motion": "^13.1.1",
"pdf-lib": "^1.17.1",
"pdf-to-printer": "^5.8.1",
"pdfjs-dist": "^3.11.174",
"pdfkit": "^0.20.2",
+ "pdfmake": "^0.1.72",
"react": "^19.2.0",
"react-dom": "^19.2.0",
- "react-icons": "^4.11.0",
+ "react-icons": "^4.12.0",
"react-pdf": "^8.0.0",
"react-zoom-pan-pinch": "^3.3.0",
"sharp": "^0.35.4",
diff --git a/frontend/public/electron.js b/frontend/public/electron.js
index d55deee..1b755a5 100644
--- a/frontend/public/electron.js
+++ b/frontend/public/electron.js
@@ -7,6 +7,7 @@ const axios = require('axios');
const { logger } = require('../../backend/src/config/logger');
const config = require('../src/config.json')
const { print } = require('pdf-to-printer');
+const { generatePayslipPDF } = require('./pdfGenerator');
protocol.registerSchemesAsPrivileged([
{
@@ -51,6 +52,532 @@ try {
global.allowClose = false;
+// ============================================
+// 📄 IPC ОБРАБОТЧИКИ
+// ============================================
+
+// ✅ 1. Печать PDF (принимает URL или base64) - СКРЫТАЯ ПЕЧАТЬ
+ipcMain.handle('print-pdf', async (event, data = {}) => {
+ let tempFile = null;
+
+ try {
+ if (!data.pdfUrl && !data.pdfBase64) {
+ throw new Error('URL или base64 PDF не передан');
+ }
+
+ let pdfBuffer;
+
+ // ✅ Если передан base64 — используем его
+ if (data.pdfBase64) {
+ logger.info('[PRINT] Печать PDF из base64');
+ pdfBuffer = Buffer.from(data.pdfBase64, 'base64');
+ logger.info('[PRINT] Размер PDF из base64:', pdfBuffer.length, 'байт');
+ }
+ // ✅ Если передан URL — скачиваем
+ else if (data.pdfUrl) {
+ const cleanUrl = data.pdfUrl.split('#')[0];
+ logger.info('[PRINT] Загрузка PDF из URL:', cleanUrl);
+
+ // Если это blob URL — получаем через fetch
+ if (cleanUrl.startsWith('blob:')) {
+ const response = await fetch(cleanUrl);
+ const blob = await response.blob();
+ pdfBuffer = Buffer.from(await blob.arrayBuffer());
+ } else {
+ const response = await axios.get(cleanUrl, {
+ responseType: 'arraybuffer',
+ timeout: 30000
+ });
+ pdfBuffer = Buffer.from(response.data);
+ }
+ logger.info('[PRINT] Размер PDF из URL:', pdfBuffer.length, 'байт');
+ }
+
+ if (!pdfBuffer || pdfBuffer.length < 100) {
+ throw new Error('PDF слишком маленький или пустой');
+ }
+
+ // ✅ ВРЕМЕННО СОХРАНЯЕМ ДЛЯ ПЕЧАТИ (pdf-to-printer требует файл)
+ const tempDir = app.getPath('temp');
+ tempFile = path.join(tempDir, `print_${Date.now()}.pdf`);
+ fs.writeFileSync(tempFile, pdfBuffer);
+ logger.info('[PRINT] Временный файл создан:', tempFile);
+
+ const printerName = data.deviceName || 'GP-80250 Series';
+ logger.info('[PRINT] СКРЫТАЯ печать через pdf-to-printer:', printerName);
+
+ // ✅ КЛЮЧЕВОЕ: silent: true — печать без диалога
+ await print(tempFile, {
+ printer: printerName,
+ silent: true,
+ printDialog: false
+ });
+
+ logger.info('[PRINT] ✅ Печать успешна');
+
+ // ✅ УДАЛЯЕМ ВРЕМЕННЫЙ ФАЙЛ
+ // try {
+ // if (tempFile && fs.existsSync(tempFile)) {
+ // fs.unlinkSync(tempFile);
+ // logger.info('[PRINT] Временный файл удален');
+ // }
+ // } catch (e) {
+ // logger.warn('[PRINT] Не удалось удалить временный файл:', e.message);
+ // }
+
+ return { success: true };
+
+ } catch (error) {
+ logger.error('[PRINT] ❌ Ошибка:', error.message);
+ try {
+ if (tempFile && fs.existsSync(tempFile)) {
+ fs.unlinkSync(tempFile);
+ }
+ } catch (e) {}
+ return { success: false, error: error.message };
+ }
+});
+
+// ✅ 2. ОСНОВНАЯ ЛОГИКА ПЕЧАТИ HTML (оставлена для совместимости)
+async function printHtmlDirectLogic(data) {
+ let tempHtml = null;
+ let tempPdf = null;
+ let printWindow = null;
+ let savedPdfPath = null;
+
+ try {
+ if (!data.html) {
+ throw new Error('HTML-код не передан');
+ }
+
+ logger.info('[PRINT-HTML] ========== СОХРАНЕНИЕ PDF ==========');
+ logger.info('[PRINT-HTML] Длина HTML:', data.html.length);
+
+ const desktopPath = app.getPath('desktop');
+ const documentsPath = app.getPath('documents');
+
+ let saveDir = documentsPath;
+ const possiblePaths = [
+ path.join(documentsPath, 'payslips'),
+ path.join(documentsPath, 'Расчетные листки'),
+ path.join(desktopPath, 'payslips'),
+ path.join(desktopPath, 'Расчетные листки'),
+ documentsPath
+ ];
+
+ for (const dir of possiblePaths) {
+ if (fs.existsSync(dir)) {
+ saveDir = dir;
+ logger.info('[PRINT-HTML] Найдена папка для сохранения:', saveDir);
+ break;
+ }
+ }
+
+ const now = new Date();
+ const dateStr = now.getFullYear() +
+ String(now.getMonth() + 1).padStart(2, '0') +
+ String(now.getDate()).padStart(2, '0') + '_' +
+ String(now.getHours()).padStart(2, '0') +
+ String(now.getMinutes()).padStart(2, '0') +
+ String(now.getSeconds()).padStart(2, '0');
+
+ const fileName = `test_${dateStr}.pdf`;
+ savedPdfPath = path.join(saveDir, fileName);
+
+ logger.info('[PRINT-HTML] PDF будет сохранен в:', savedPdfPath);
+
+ const tempDir = app.getPath('temp');
+ tempHtml = path.join(tempDir, `print_${Date.now()}.html`);
+ tempPdf = path.join(tempDir, `print_${Date.now()}.pdf`);
+
+ let htmlContent = data.html;
+ if (!htmlContent || htmlContent.trim().length < 10) {
+ logger.warn('[PRINT-HTML] HTML пустой, используем тестовый');
+ htmlContent = `
+
+
ТЕСТ ПЕЧАТИ
+
+
Дата: ${new Date().toLocaleString()}
+
Табельный номер: TEST
+
+ `;
+ }
+
+ const fullHtml = `
+
+
+
+
Расчетный листок
+
+
+
+ ${htmlContent}
+
+ Сгенерировано: ${new Date().toLocaleString()}
+
+
+`;
+
+ fs.writeFileSync(tempHtml, fullHtml, 'utf8');
+ logger.info('[PRINT-HTML] HTML файл создан:', tempHtml);
+ logger.info('[PRINT-HTML] Размер HTML:', fs.statSync(tempHtml).size, 'байт');
+
+ printWindow = new BrowserWindow({
+ width: 600,
+ height: 800,
+ show: true,
+ webPreferences: {
+ nodeIntegration: false,
+ contextIsolation: true,
+ webSecurity: false,
+ allowRunningInsecureContent: true,
+ }
+ });
+
+ printWindow.webContents.openDevTools();
+
+ logger.info('[PRINT-HTML] Загрузка HTML в окно...');
+ await printWindow.loadFile(tempHtml);
+
+ await new Promise((resolve) => {
+ printWindow.webContents.once('did-finish-load', () => {
+ logger.info('[PRINT-HTML] Событие did-finish-load');
+ resolve();
+ });
+ setTimeout(() => {
+ logger.info('[PRINT-HTML] Таймаут загрузки');
+ resolve();
+ }, 10000);
+ });
+
+ const contentCheck = await printWindow.webContents.executeJavaScript(`
+ (function() {
+ const body = document.body;
+ if (!body) return { hasBody: false, contentLength: 0 };
+ const text = body.textContent || '';
+ const html = body.innerHTML || '';
+ return {
+ hasBody: true,
+ contentLength: text.length,
+ htmlLength: html.length,
+ preview: text.substring(0, 200)
+ };
+ })()
+ `);
+
+ logger.info('[PRINT-HTML] Проверка контента:', contentCheck);
+
+ if (contentCheck.contentLength < 10) {
+ logger.warn('[PRINT-HTML] ⚠️ В окне нет контента!');
+ await printWindow.webContents.executeJavaScript(`
+ if (!document.body || document.body.textContent.length < 10) {
+ document.body.innerHTML = '
ОШИБКА: Контент не загрузился!
';
+ }
+ `);
+ }
+
+ await printWindow.webContents.executeJavaScript(`
+ document.body.style.display = 'block';
+ document.body.style.visibility = 'visible';
+ document.body.style.opacity = '1';
+ window.scrollTo(0, 0);
+ document.querySelectorAll('*').forEach(el => {
+ el.style.display = 'block';
+ el.style.visibility = 'visible';
+ el.style.opacity = '1';
+ });
+ console.log('Контент в окне:', document.body.textContent.length, 'символов');
+ console.log('HTML:', document.body.innerHTML.substring(0, 500));
+ `);
+
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ const finalCheck = await printWindow.webContents.executeJavaScript(`
+ document.body ? document.body.textContent.length : 0
+ `);
+ logger.info('[PRINT-HTML] Финальная проверка контента:', finalCheck, 'символов');
+
+ logger.info('[PRINT-HTML] Конвертация в PDF...');
+ const pdfData = await printWindow.webContents.printToPDF({
+ pageSize: {
+ width: 226800,
+ height: 500000
+ },
+ margins: {
+ top: 0,
+ bottom: 0,
+ left: 0,
+ right: 0
+ },
+ printBackground: true,
+ landscape: false
+ });
+
+ fs.writeFileSync(tempPdf, pdfData);
+ logger.info('[PRINT-HTML] PDF создан, размер:', pdfData.length, 'байт');
+
+ const pdfBuffer = fs.readFileSync(tempPdf);
+ const isPdf = pdfBuffer.slice(0, 4).toString() === '%PDF';
+ logger.info('[PRINT-HTML] Это PDF?', isPdf);
+
+ if (!isPdf) {
+ logger.error('[PRINT-HTML] ⚠️ Файл НЕ является PDF!');
+ throw new Error('Созданный файл не является PDF');
+ }
+
+ if (printWindow) {
+ printWindow.close();
+ printWindow = null;
+ }
+
+ if (pdfData.length < 500) {
+ logger.warn('[PRINT-HTML] ⚠️ PDF слишком маленький:', pdfData.length, 'байт');
+ logger.info('[PRINT-HTML] Пробуем еще раз с большим ожиданием...');
+
+ const retryWindow = new BrowserWindow({
+ width: 600,
+ height: 800,
+ show: false,
+ webPreferences: {
+ nodeIntegration: false,
+ contextIsolation: true,
+ webSecurity: false,
+ allowRunningInsecureContent: true,
+ }
+ });
+
+ await retryWindow.loadFile(tempHtml);
+ await new Promise(resolve => setTimeout(resolve, 3000));
+
+ const retryPdf = await retryWindow.webContents.printToPDF({
+ pageSize: { width: 226800, height: 500000 },
+ margins: { top: 0, bottom: 0, left: 0, right: 0 },
+ printBackground: true,
+ landscape: false
+ });
+
+ fs.writeFileSync(tempPdf, retryPdf);
+ logger.info('[PRINT-HTML] Повторный PDF создан, размер:', retryPdf.length, 'байт');
+ retryWindow.close();
+ }
+
+ fs.copyFileSync(tempPdf, savedPdfPath);
+ logger.info('[PRINT-HTML] ✅ PDF сохранен в:', savedPdfPath);
+ logger.info('[PRINT-HTML] Размер сохраненного PDF:', fs.statSync(savedPdfPath).size, 'байт');
+
+ const printerName = data.printerName || 'GP-80250 Series';
+ logger.info('[PRINT-HTML] Отправка PDF на печать, принтер:', printerName);
+
+ let printResult = null;
+ let printSuccess = false;
+
+ try {
+ logger.info('[PRINT-HTML] Попытка печати...');
+ printResult = await print(tempPdf, {
+ printer: printerName,
+ scale: 'fit',
+ paperSize: 'A4'
+ });
+ printSuccess = true;
+ logger.info('[PRINT-HTML] Печать успешна:', printResult);
+ } catch (err) {
+ logger.warn('[PRINT-HTML] Печать не удалась:', err.message);
+ try {
+ printResult = await print(tempPdf);
+ printSuccess = true;
+ logger.info('[PRINT-HTML] Печать успешна (без параметров)');
+ } catch (err2) {
+ logger.error('[PRINT-HTML] Все попытки печати не удались:', err2.message);
+ }
+ }
+
+ try {
+ if (tempHtml && fs.existsSync(tempHtml)) fs.unlinkSync(tempHtml);
+ if (tempPdf && fs.existsSync(tempPdf)) fs.unlinkSync(tempPdf);
+ } catch (e) {}
+
+ logger.info('[PRINT-HTML] ========== ЗАВЕРШЕНО ==========');
+ logger.info('[PRINT-HTML] ✅ PDF сохранен в:', savedPdfPath);
+
+ return {
+ success: true,
+ pdfPath: savedPdfPath,
+ printSuccess: printSuccess,
+ message: `PDF сохранен в: ${savedPdfPath}`
+ };
+
+ } catch (error) {
+ logger.error('[PRINT-HTML] ❌ Ошибка:', error.message);
+ logger.error('[PRINT-HTML] Стек:', error.stack);
+
+ try {
+ if (printWindow) printWindow.close();
+ if (tempHtml && fs.existsSync(tempHtml)) fs.unlinkSync(tempHtml);
+ if (tempPdf && fs.existsSync(tempPdf)) fs.unlinkSync(tempPdf);
+ } catch (e) {}
+
+ return { success: false, error: error.message };
+ }
+}
+
+// ✅ 3. Обработчик print-html-direct (для совместимости)
+ipcMain.handle('print-html-direct', async (event, data) => {
+ return await printHtmlDirectLogic(data);
+});
+
+// ✅ 4. ОСНОВНОЙ ОБРАБОТЧИК - ГЕНЕРАЦИЯ PDF В ПАМЯТИ (БЕЗ СОХРАНЕНИЯ НА ДИСК)
+ipcMain.handle('print-payslip-form', async (event, data = {}) => {
+ try {
+ logger.info('[PRINT-PAYSLIP] ========== ГЕНЕРАЦИЯ PDF В ПАМЯТИ ==========');
+ logger.info('[PRINT-PAYSLIP] tnom:', data.tnom);
+ logger.info('[PRINT-PAYSLIP] period:', data.period);
+
+ // --- 1. Получаем данные из бэкенда ---
+ let payslipData = null;
+
+ try {
+ const backendUrl = appConfig.backendUrl || 'http://localhost:3001';
+ const url = `${backendUrl}/api/payslip/data/${data.tnom}?period=${data.period}`;
+ logger.info('[PRINT-PAYSLIP] Запрос к бэкенду:', url);
+
+ const response = await axios.get(url, { timeout: 10000 });
+ payslipData = response.data;
+ logger.info('[PRINT-PAYSLIP] Данные получены');
+ } catch (error) {
+ logger.warn('[PRINT-PAYSLIP] Не удалось получить данные из бэкенда:', error.message);
+ // Заглушка на случай, если бэкенд не отвечает
+ payslipData = {
+ fullName: 'Иванов Иван Иванович',
+ tabNumber: data.tnom || '00001',
+ department: 'Отдел разработки',
+ position: 'Инженер-программист',
+ daysWorked: '22',
+ month: 'Ноябрь',
+ year: '2025',
+ period: data.period || '112025',
+ accrued: [
+ { code: '01', description: 'Оклад', amount: 2891.87 },
+ { code: '02', description: 'Премия', amount: 0 }
+ ],
+ totalAccrued: 2891.87,
+ deductions: [
+ { code: '101', description: 'НДФЛ', amount: 376.36 },
+ { code: '102', description: 'Профсоюз', amount: 85.50 }
+ ],
+ totalDeductions: 461.86,
+ totalPaid: 2430.01,
+ bankTransfers: [
+ { bank: 'Сбербанк', amount: 2430.01 }
+ ]
+ };
+ }
+
+ // --- 2. Генерируем PDF в памяти (Buffer) ---
+ const pdfBuffer = await generatePayslipPDF(payslipData);
+ logger.info('[PRINT-PAYSLIP] PDF сгенерирован, размер:', pdfBuffer.length, 'байт');
+
+ if (pdfBuffer.length < 100) {
+ throw new Error('Сгенерированный PDF слишком маленький');
+ }
+
+ // --- 3. Преобразуем Buffer в base64 для передачи через IPC ---
+ const pdfBase64 = pdfBuffer.toString('base64');
+
+ logger.info('[PRINT-PAYSLIP] PDF сконвертирован в base64, длина:', pdfBase64.length);
+
+ // --- 4. Возвращаем base64 PDF ---
+ return {
+ success: true,
+ pdfBase64: pdfBase64,
+ fileName: `${data.tnom || 'payslip'}.pdf`
+ };
+
+ } catch (error) {
+ logger.error('[PRINT-PAYSLIP] ❌ Ошибка:', error.message);
+ logger.error('[PRINT-PAYSLIP] Стек:', error.stack);
+ return { success: false, error: error.message };
+ }
+});
+
+// ✅ 5. Обработчик получения списка принтеров
+ipcMain.handle('get-printers', async () => {
+ return [
+ {
+ name: 'GP-80250 Series',
+ displayName: 'GP-80250 Series',
+ isDefault: true,
+ status: 'ready'
+ }
+ ];
+});
+
+// ✅ 6. Обработчик получения конфига
+ipcMain.handle('get-app-config', async (event) => {
+ return appConfig;
+});
+
+// ============================================
+// 🚀 СОЗДАНИЕ ГЛАВНОГО ОКНА
+// ============================================
+
function createWindow() {
const preloadPath = path.join(__dirname, 'preload.js');
try {
@@ -90,94 +617,6 @@ function createWindow() {
});
}
-// ============================================
-// 📄 ПЕЧАТЬ PDF ЧЕРЕЗ pdf-to-printer
-// ============================================
-
-ipcMain.handle('print-pdf', async (event, data = {}) => {
- let tempFile = null;
-
- try {
- if (!data.pdfUrl) {
- throw new Error('URL файла PDF не передан');
- }
-
- const cleanUrl = data.pdfUrl.split('#')[0];
- logger.info('[PRINT] Загрузка PDF:', cleanUrl);
-
- // ✅ Скачиваем PDF
- const response = await axios.get(cleanUrl, {
- responseType: 'arraybuffer',
- timeout: 30000
- });
-
- const pdfBuffer = Buffer.from(response.data);
- logger.info('[PRINT] Размер PDF:', pdfBuffer.length, 'байт');
-
- if (pdfBuffer.length < 100) {
- throw new Error('PDF слишком маленький или пустой');
- }
-
- // ✅ Сохраняем во временный файл
- const tempDir = app.getPath('temp');
- tempFile = path.join(tempDir, `print_${Date.now()}.pdf`);
- fs.writeFileSync(tempFile, pdfBuffer);
- logger.info('[PRINT] Временный файл создан:', tempFile);
-
- // ✅ ПЕЧАТАЕМ ЧЕРЕЗ pdf-to-printer
- const printerName = 'GP-80250 Series';
- logger.info('[PRINT] Отправляем на печать через pdf-to-printer:', printerName);
-
- // ✅ Опции печати
- const options = {
- printer: printerName,
- // scale: 'fit', // Масштабирование
- // paperSize: 'A4',
- // landscape: false,
- // copies: 1,
- // silent: true // Тихая печать
- };
-
- const result = await print(tempFile, options);
- logger.info('[PRINT] Результат печати:', result);
-
- // ✅ Удаляем временный файл
- try {
- if (tempFile && fs.existsSync(tempFile)) {
- fs.unlinkSync(tempFile);
- logger.info('[PRINT] Временный файл удален');
- }
- } catch (e) {}
-
- logger.info('[PRINT] ✅ Документ отправлен на печать');
- return true;
-
- } catch (error) {
- logger.error('[PRINT] ❌ Ошибка:', error.message);
- try {
- if (tempFile && fs.existsSync(tempFile)) {
- fs.unlinkSync(tempFile);
- }
- } catch (e) {}
- throw error;
- }
-});
-
-// ============================================
-// 📄 ПОЛУЧЕНИЕ СПИСКА ПРИНТЕРОВ
-// ============================================
-
-ipcMain.handle('get-printers', async () => {
- return [
- {
- name: 'GP-80250 Series',
- displayName: 'GP-80250 Series',
- isDefault: true,
- status: 'ready'
- }
- ];
-});
-
// ============================================
// 🚀 ЗАПУСК
// ============================================
@@ -226,8 +665,4 @@ app.on('activate', () => {
app.on('will-quit', () => {
globalShortcut.unregisterAll();
-});
-
-ipcMain.handle('get-app-config', async (event) => {
- return appConfig;
});
\ No newline at end of file
diff --git a/frontend/public/payslips/00001_20260902.pdf b/frontend/public/payslips/00001_20260902.pdf
new file mode 100644
index 0000000..36c7765
Binary files /dev/null and b/frontend/public/payslips/00001_20260902.pdf differ
diff --git a/frontend/public/payslips/001.pdf b/frontend/public/payslips/001.pdf
new file mode 100644
index 0000000..9157731
Binary files /dev/null and b/frontend/public/payslips/001.pdf differ
diff --git a/frontend/public/payslips/001_20260902.pdf b/frontend/public/payslips/001_20260902.pdf
new file mode 100644
index 0000000..474ea37
Binary files /dev/null and b/frontend/public/payslips/001_20260902.pdf differ
diff --git a/frontend/public/pdfGenerator.js b/frontend/public/pdfGenerator.js
new file mode 100644
index 0000000..1f754dd
--- /dev/null
+++ b/frontend/public/pdfGenerator.js
@@ -0,0 +1,113 @@
+// pdfGenerator.js - АЛЬТЕРНАТИВНАЯ ВЕРСИЯ (для pdfmake 0.1.72)
+const pdfmake = require('pdfmake');
+
+// Для версии 0.1.72 используется другой способ
+const fonts = {
+ Roboto: {
+ normal: 'C:/Windows/Fonts/arial.ttf',
+ bold: 'C:/Windows/Fonts/arialbd.ttf',
+ italics: 'C:/Windows/Fonts/ariali.ttf',
+ bolditalics: 'C:/Windows/Fonts/arialbi.ttf'
+ }
+};
+
+// Для старой версии pdfmake
+const printer = new pdfmake(fonts);
+
+function buildDocDefinition(data) {
+ return {
+ pageSize: { width: 80 * 2.83465, height: 297 * 2.83465 },
+ pageMargins: [5, 5, 5, 5],
+ content: [
+ { text: 'РАСЧЕТНЫЙ ЛИСТОК', style: 'header' },
+ { text: `${data.month || ''} ${data.year || ''} (${data.period || ''})`, style: 'subheader' },
+ { text: ' ', style: 'divider' },
+
+ { text: `ФИО: ${data.fullName || ''}`, style: 'row' },
+ { text: `Таб.№: ${data.tabNumber || ''}`, style: 'row' },
+ { text: `Подразделение: ${data.department || ''}`, style: 'row' },
+ { text: `Должность: ${data.position || ''}`, style: 'row' },
+ { text: `Отработано: ${data.daysWorked || ''}`, style: 'row' },
+ { text: ' ', style: 'divider' },
+
+ { text: 'НАЧИСЛЕНО', style: 'sectionTitle' },
+ ...(data.accrued || []).map(item => ({
+ columns: [
+ { text: `${item.code || ''} ${item.description || ''}`, width: '*', style: 'itemDesc' },
+ { text: (item.amount || 0).toFixed(2), alignment: 'right', style: 'itemAmount' }
+ ],
+ style: 'itemRow'
+ })),
+ { text: `ИТОГО НАЧИСЛЕНО: ${(data.totalAccrued || 0).toFixed(2)}`, style: 'totalRow' },
+ { text: ' ', style: 'divider' },
+
+ { text: 'УДЕРЖАНО', style: 'sectionTitle' },
+ ...(data.deductions || []).map(item => ({
+ columns: [
+ { text: `${item.code || ''} ${item.description || ''}`, width: '*', style: 'itemDesc' },
+ { text: (item.amount || 0).toFixed(2), alignment: 'right', style: 'itemAmount' }
+ ],
+ style: 'itemRow'
+ })),
+ { text: `ИТОГО УДЕРЖАНО: ${(data.totalDeductions || 0).toFixed(2)}`, style: 'totalRow' },
+ { text: ' ', style: 'divider' },
+
+ { text: `К ВЫПЛАТЕ: ${(data.totalPaid || 0).toFixed(2)}`, style: 'totalRow big' },
+ { text: ' ', style: 'divider' },
+
+ ...(data.bankTransfers && data.bankTransfers.length > 0 ? [
+ { text: 'БАНКОВСКИЕ ПЕРЕВОДЫ', style: 'sectionTitle' },
+ ...data.bankTransfers.map(bank => ({
+ columns: [
+ { text: bank.bank || '', width: '*', style: 'itemDesc' },
+ { text: (bank.amount || 0).toFixed(2), alignment: 'right', style: 'itemAmount' }
+ ],
+ style: 'itemRow'
+ }))
+ ] : []),
+
+ ...(data.other && data.other.length > 0 ? [
+ { text: 'ПРОЧЕЕ', style: 'sectionTitle' },
+ ...data.other.map(item => ({
+ columns: [
+ { text: `${item.code || ''} ${item.description || ''}`, width: '*', style: 'itemDesc' },
+ { text: (item.amount || 0).toFixed(2), alignment: 'right', style: 'itemAmount' }
+ ],
+ style: 'itemRow'
+ })),
+ { text: `ИТОГО ПРОЧЕЕ: ${(data.totalOther || 0).toFixed(2)}`, style: 'totalRow' }
+ ] : []),
+
+ { text: ' ', style: 'divider' },
+ { text: `Сформировано: ${new Date().toLocaleString('ru-RU')}`, style: 'footer' }
+ ],
+ styles: {
+ header: { fontSize: 14, bold: true, alignment: 'center', margin: [0, 0, 0, 4] },
+ subheader: { fontSize: 12, alignment: 'center', margin: [0, 0, 0, 4] },
+ row: { fontSize: 10, margin: [0, 2, 0, 2] },
+ divider: { margin: [0, 4, 0, 4], border: [false, true, false, false] },
+ sectionTitle: { fontSize: 12, bold: true, margin: [0, 6, 0, 2] },
+ itemRow: { fontSize: 10, margin: [0, 1, 0, 1] },
+ itemDesc: { fontSize: 10 },
+ itemAmount: { fontSize: 10, alignment: 'right' },
+ totalRow: { fontSize: 12, bold: true, margin: [0, 4, 0, 0] },
+ 'totalRow big': { fontSize: 14, bold: true, margin: [0, 8, 0, 0] },
+ footer: { fontSize: 8, alignment: 'center', color: '#666', margin: [0, 8, 0, 0] }
+ }
+ };
+}
+
+function generatePayslipPDF(data) {
+ const docDefinition = buildDocDefinition(data);
+ const pdfDoc = printer.createPdfKitDocument(docDefinition);
+
+ return new Promise((resolve, reject) => {
+ const chunks = [];
+ pdfDoc.on('data', chunk => chunks.push(chunk));
+ pdfDoc.on('end', () => resolve(Buffer.concat(chunks)));
+ pdfDoc.on('error', reject);
+ pdfDoc.end();
+ });
+}
+
+module.exports = { generatePayslipPDF };
\ No newline at end of file
diff --git a/frontend/public/preload.js b/frontend/public/preload.js
index 223cfbb..85ffef6 100644
--- a/frontend/public/preload.js
+++ b/frontend/public/preload.js
@@ -19,6 +19,8 @@ contextBridge.exposeInMainWorld(
},
printPdf: (options) => ipcRenderer.invoke('print-pdf', options),
getPrinters: () => ipcRenderer.invoke('get-printers'),
+ // ✅ НОВАЯ ФУНКЦИЯ: печать формы расчетного листка
+ printPayslipForm: (data) => ipcRenderer.invoke('print-payslip-form', data),
}
);
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 2815b8c..40e1777 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -20,6 +20,7 @@ import LaborSafety from './components/LaborSafety';
import Contacts from './components/Contacts';
import News from './components/News';
import Profile from './components/Profile';
+import FoodMenu from './components/FoodMenu';
interface Employee {
id: string;
@@ -303,6 +304,8 @@ function App() {
return
;
case 'profile':
return
;
+ case 'food':
+ return
setCurrentSection('main')} employee={employee} />;
default:
return ;
}
diff --git a/frontend/src/components/BottomBar.tsx b/frontend/src/components/BottomBar.tsx
index 046d23a..322f128 100644
--- a/frontend/src/components/BottomBar.tsx
+++ b/frontend/src/components/BottomBar.tsx
@@ -37,7 +37,7 @@ const BottomBar: React.FC = () => {
const BACKEND_URL = 'http://localhost:3001';
- // ============================================
+ // ============================================
// 📊 ЗАГРУЗКА КУРСОВ
// ============================================
useEffect(() => {
@@ -45,57 +45,29 @@ const BottomBar: React.FC = () => {
try {
setLoading(true);
- // ✅ Сначала пробуем MySQL
- try {
- const mysqlResponse = await fetch(`${BACKEND_URL}/api/rates/mysql`);
- if (mysqlResponse.ok) {
- const data = await mysqlResponse.json();
- if (data.currency && data.currency.length > 0) {
- setCurrencyRates(data.currency);
- setMetalRates(data.metals || []);
- setDataSource('mysql');
- setLastUpdate(new Date().toLocaleString('ru-RU'));
- // Сохраняем в localStorage
- localStorage.setItem('ratesData', JSON.stringify({
- currency: data.currency,
- metals: data.metals,
- source: 'mysql',
- lastUpdate: new Date().toISOString()
- }));
- setLoading(false);
- return;
- }
+ // ✅ Запрашиваем единый эндпоинт, который уже есть на бэкенде
+ const response = await fetch(`${BACKEND_URL}/api/rates`);
+ if (response.ok) {
+ const data = await response.json();
+ if (data.currency && data.currency.length > 0) {
+ setCurrencyRates(data.currency);
+ setMetalRates(data.metals || []);
+ setDataSource(data.source || 'default');
+ setLastUpdate(new Date().toLocaleString('ru-RU'));
+
+ // Сохраняем в localStorage для оффлайн-режима
+ localStorage.setItem('ratesData', JSON.stringify({
+ currency: data.currency,
+ metals: data.metals,
+ source: data.source,
+ lastUpdate: new Date().toISOString()
+ }));
+ setLoading(false);
+ return;
}
- } catch (mysqlError) {
- console.warn('MySQL недоступен, пробуем Excel:', mysqlError);
}
-
- // ✅ Если MySQL не работает — пробуем Excel
- try {
- const excelResponse = await fetch(`${BACKEND_URL}/api/rates/excel`);
- if (excelResponse.ok) {
- const data = await excelResponse.json();
- if (data.currency && data.currency.length > 0) {
- setCurrencyRates(data.currency);
- setMetalRates(data.metals || []);
- setDataSource('excel');
- setLastUpdate(new Date().toLocaleString('ru-RU'));
- // Сохраняем в localStorage
- localStorage.setItem('ratesData', JSON.stringify({
- currency: data.currency,
- metals: data.metals,
- source: 'excel',
- lastUpdate: new Date().toISOString()
- }));
- setLoading(false);
- return;
- }
- }
- } catch (excelError) {
- console.warn('Excel недоступен:', excelError);
- }
-
- // ✅ Если ничего не работает — берём из localStorage
+
+ // Если не удалось — берём из localStorage
const stored = localStorage.getItem('ratesData');
if (stored) {
try {
@@ -114,7 +86,6 @@ const BottomBar: React.FC = () => {
}
setLoading(false);
-
} catch (error) {
console.warn('Ошибка загрузки курсов:', error);
setLoading(false);
diff --git a/frontend/src/components/EmployeeInfo.tsx b/frontend/src/components/EmployeeInfo.tsx
index f2bfb03..5ad0d6d 100644
--- a/frontend/src/components/EmployeeInfo.tsx
+++ b/frontend/src/components/EmployeeInfo.tsx
@@ -66,11 +66,7 @@ const EmployeeInfo: React.FC = ({
const periods = generatePeriods();
const monthNames = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
- 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
-
- const getFullUrl = (url: string) => {
- return url.startsWith('http') ? url : `http://localhost:3001${url}`;
- };
+ 'Июль', 'Август', 'Сентябрь', 'Окторябрь', 'Ноябрь', 'Декабрь'];
const checkPayslip = async () => {
if (!employee?.tnom) return;
@@ -100,6 +96,7 @@ const EmployeeInfo: React.FC = ({
checkPayslip();
}, [selectedPeriod, employee]);
+ // ✅ ФУНКЦИЯ: формируем PDF в памяти и открываем предпросмотр
const handlePreviewPdf = async () => {
if (!employee?.tnom) return;
@@ -109,6 +106,50 @@ const EmployeeInfo: React.FC = ({
setIsFullscreen(false);
try {
+ const electronAPI = (window as any).electronAPI;
+
+ console.log('🔍 [PREVIEW] electronAPI доступен?', !!electronAPI);
+
+ if (electronAPI && electronAPI.printPayslipForm) {
+ console.log('🔄 [PREVIEW] Формируем PDF через Electron...');
+
+ const result = await electronAPI.printPayslipForm({
+ tnom: employee.tnom,
+ period: selectedPeriod,
+ printerName: 'GP-80250 Series'
+ });
+
+ console.log('📄 [PREVIEW] Результат от Electron:', result);
+
+ if (result && result.success === false) {
+ throw new Error(result.error || 'Ошибка формирования PDF');
+ }
+
+ if (result && result.pdfBase64) {
+ // ✅ Сохраняем base64 для печати
+ (window as any).__pdfBase64 = result.pdfBase64;
+
+ // ✅ Преобразуем base64 в Blob URL для предпросмотра
+ const byteCharacters = atob(result.pdfBase64);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ const blob = new Blob([byteArray], { type: 'application/pdf' });
+ const blobUrl = URL.createObjectURL(blob);
+
+ setPayslipUrl(blobUrl);
+ setPdfLoading(false);
+ console.log('✅ [PREVIEW] PDF загружен в Blob URL');
+ return;
+ } else {
+ throw new Error('PDF не создан');
+ }
+ }
+
+ // ✅ Fallback: используем старый метод через бэкенд
+ console.log('🔄 [PREVIEW] Используем fallback через бэкенд...');
const backendUrl = config.backendUrl || 'http://localhost:3001';
const response = await fetch(`${backendUrl}/api/payslip/url/${employee.tnom}?period=${selectedPeriod}`);
@@ -127,8 +168,8 @@ const EmployeeInfo: React.FC = ({
setPdfLoading(false);
}
} catch (error) {
- console.error('Ошибка загрузки PDF:', error);
- setPdfError('Не удалось загрузить расчетный листок');
+ console.error('❌ [PREVIEW] Ошибка формирования PDF:', error);
+ setPdfError(error instanceof Error ? error.message : 'Не удалось сформировать расчетный листок');
setPdfLoading(false);
}
};
@@ -138,63 +179,47 @@ const EmployeeInfo: React.FC = ({
setPdfLoading(false);
setPdfError(null);
setIsFullscreen(false);
+
+ // Очищаем URL, чтобы не было утечек памяти
+ if (payslipUrl && payslipUrl.startsWith('blob:')) {
+ URL.revokeObjectURL(payslipUrl);
+ }
+ setPayslipUrl(null);
};
- const handlePrint = async () => {
+ // ✅ ФУНКЦИЯ ПЕЧАТИ (отправляет base64 напрямую в print-pdf)
+ const handlePrint = async () => {
try {
setIsPrinting(true);
setPrintStatus('sending');
setPrintMessage('Отправка документа на печать...');
- const urlToPrint = payslipUrl ; // payslipUrl для Employee/Finance, fileUrl для Viewer
- if (!urlToPrint) throw new Error('URL документа отсутствует');
+ const electronAPI = (window as any).electronAPI;
- const fullUrl = urlToPrint.startsWith('http') ? urlToPrint : `http://localhost:3001${urlToPrint}`;
- // 🚀 ВАРИАНТ 1: Мы в десктопе (Electron)
- if ((window as any).electronAPI && (window as any).electronAPI.printPdf) {
- console.log('🖨️ [PRINT] Печатаем тихо через Electron API');
- await (window as any).electronAPI.printPdf({
- pdfUrl: fullUrl,
- deviceName: '' // <-- Оставляем пустым! Electron сам плюнет на дефолтный принтер
+ // ✅ Получаем base64 из памяти
+ const pdfBase64 = (window as any).__pdfBase64;
+
+ if (!pdfBase64) {
+ throw new Error('PDF не сформирован. Сначала откройте предпросмотр.');
+ }
+
+ if (electronAPI && electronAPI.printPdf) {
+ console.log('🖨️ [PRINT] Печатаем PDF из памяти (base64)');
+
+ // ✅ Отправляем base64 напрямую в print-pdf
+ const result = await electronAPI.printPdf({
+ pdfBase64: pdfBase64,
+ deviceName: 'GP-80250 Series'
});
- setPrintStatus('success');
- setPrintMessage('✅ Отправлено на принтер');
- }
- // 🌐 ВАРИАНТ 2: Мы в браузере (Chrome/Edge)
- else {
- console.log('🖨️ [PRINT] Electron API нет. Обходим CORS и печатаем через браузер');
-
- // Скачиваем PDF как Blob, чтобы сделать его "своим" для браузера
- const response = await fetch(fullUrl);
- if (!response.ok) throw new Error('Не удалось скачать PDF с сервера');
-
- const blob = await response.blob();
- const blobUrl = URL.createObjectURL(blob);
-
- // Создаем невидимый iframe
- const printIframe = document.createElement('iframe');
- printIframe.style.display = 'none';
- printIframe.src = blobUrl;
- document.body.appendChild(printIframe);
-
- printIframe.onload = () => {
- try {
- printIframe.contentWindow?.focus();
- printIframe.contentWindow?.print();
- setPrintStatus('success');
- setPrintMessage('✅ Документ отправлен на печать');
- } catch (e) {
- console.error('Ошибка iframe печати:', e);
- throw new Error('Браузер заблокировал печать');
- } finally {
- // Чистим за собой мусор через 5 секунд
- setTimeout(() => {
- document.body.removeChild(printIframe);
- URL.revokeObjectURL(blobUrl);
- }, 5000);
- }
- };
+ if (result && result.success) {
+ setPrintStatus('success');
+ setPrintMessage('✅ Отправлено на принтер');
+ } else {
+ throw new Error(result?.error || 'Ошибка печати');
+ }
+ } else {
+ throw new Error('Electron API не доступен');
}
} catch (error) {
@@ -400,23 +425,24 @@ const EmployeeInfo: React.FC = ({
) : null}
+ {/* ✅ ТОЛЬКО ОДНА КНОПКА - ПРЕДПРОСМОТР */}