Compare commits

...

1 Commits

Author SHA1 Message Date
0e23876dcd feat: add Vue 3 interface with modern UI/UX
- Dodano Vue 3 framework do projektu
- Komponenty: App.vue, InvoiceGenerator.vue, UpoGenerator.vue
- Nowoczesny interfejs z gradientami i animacjami
- File upload z drag-n-drop obsługą
- Progress bar visualizacja
- Message notifications (success/error)
- Responsywny design dla mobile/desktop
- Copyright link do CIRFMF na GitHub
- Build:app script do Vite
- vue oraz @vitejs/plugin-vue instalacja
2026-03-18 23:02:32 +01:00
12 changed files with 1538 additions and 437 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,16 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="pl">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>XML Parser</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="module" crossorigin src="/ksef/assets/index-BOCrZ8tn.js"></script> <title>KSEF PDF Generator - Vue 3</title>
<script type="module" crossorigin src="/ksef/assets/index-Dw4i_fPq.js"></script>
<link rel="stylesheet" crossorigin href="/ksef/assets/index-RaXg1fq2.css">
</head> </head>
<body> <body>
<h1>📄 XML Parser</h1> <div id="app"></div>
<h2>Wygeneruj fakture:</h2>
<input
accept=".xml"
id="xmlInput"
type="file"
/>
<h2>Wygeneruj UPO:</h2>
<input
accept=".xml"
id="xmlInputUPO"
type="file"
/>
</body> </body>
</html> </html>

231
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"pdfmake": "^0.2.20", "pdfmake": "^0.2.20",
"vue": "^3.5.30",
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
}, },
"devDependencies": { "devDependencies": {
@@ -18,6 +19,7 @@
"@types/pdfmake": "^0.2.11", "@types/pdfmake": "^0.2.11",
"@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0", "@typescript-eslint/parser": "^8.40.0",
"@vitejs/plugin-vue": "^6.0.5",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.33.0", "eslint": "^9.33.0",
@@ -40,26 +42,27 @@
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.27.1",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.28.5",
"dev": true, "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.28.4", "version": "7.29.2",
"dev": true, "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.4" "@babel/types": "^7.29.0"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -68,13 +71,14 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@babel/parser/node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.28.4", "version": "7.29.0",
"dev": true, "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.28.5"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -736,7 +740,6 @@
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
@@ -781,6 +784,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
"integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.4", "version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
@@ -1722,6 +1732,23 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@vitejs/plugin-vue": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz",
"integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-rc.2"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "3.2.4", "version": "3.2.4",
"dev": true, "dev": true,
@@ -1930,40 +1957,65 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.22", "version": "3.5.30",
"dev": true, "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
"integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.4", "@babel/parser": "^7.29.0",
"@vue/shared": "3.5.22", "@vue/shared": "3.5.30",
"entities": "^4.5.0", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-core/node_modules/entities": {
"version": "4.5.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/@vue/compiler-core/node_modules/estree-walker": { "node_modules/@vue/compiler-core/node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"dev": true, "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.22", "version": "3.5.30",
"dev": true, "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
"integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.22", "@vue/compiler-core": "3.5.30",
"@vue/shared": "3.5.22" "@vue/shared": "3.5.30"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
"integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/compiler-core": "3.5.30",
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.8",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
"integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/shared": "3.5.30"
} }
}, },
"node_modules/@vue/compiler-vue2": { "node_modules/@vue/compiler-vue2": {
@@ -1975,9 +2027,54 @@
"he": "^1.2.0" "he": "^1.2.0"
} }
}, },
"node_modules/@vue/reactivity": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
"integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.30"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
"integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/shared": "3.5.30"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
"integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.30",
"@vue/runtime-core": "3.5.30",
"@vue/shared": "3.5.30",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
"integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.30",
"@vue/shared": "3.5.30"
},
"peerDependencies": {
"vue": "3.5.30"
}
},
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.22", "version": "3.5.30",
"dev": true, "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
"integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/acorn": { "node_modules/acorn": {
@@ -2268,6 +2365,12 @@
"lru-cache": "^10.4.3" "lru-cache": "^10.4.3"
} }
}, },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/data-urls": { "node_modules/data-urls": {
"version": "5.0.0", "version": "5.0.0",
"dev": true, "dev": true,
@@ -2393,6 +2496,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"license": "MIT", "license": "MIT",
@@ -3688,8 +3803,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.19", "version": "0.30.21",
"dev": true, "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
@@ -3705,18 +3821,6 @@
"source-map-js": "^1.2.0" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/magicast/node_modules/@babel/types": {
"version": "7.28.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/make-dir": { "node_modules/make-dir": {
"version": "4.0.0", "version": "4.0.0",
"dev": true, "dev": true,
@@ -3785,7 +3889,6 @@
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -3971,7 +4074,6 @@
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -3999,8 +4101,9 @@
"version": "1.0.0" "version": "1.0.0"
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.8",
"dev": true, "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -4352,7 +4455,6 @@
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -4663,7 +4765,7 @@
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.9.3", "version": "5.9.3",
"dev": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@@ -5468,6 +5570,27 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/vue": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-sfc": "3.5.30",
"@vue/runtime-dom": "3.5.30",
"@vue/server-renderer": "3.5.30",
"@vue/shared": "3.5.30"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/w3c-xmlserializer": { "node_modules/w3c-xmlserializer": {
"version": "5.0.0", "version": "5.0.0",
"dev": true, "dev": true,

View File

@@ -34,6 +34,7 @@
"@types/pdfmake": "^0.2.11", "@types/pdfmake": "^0.2.11",
"@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0", "@typescript-eslint/parser": "^8.40.0",
"@vitejs/plugin-vue": "^6.0.5",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.33.0", "eslint": "^9.33.0",
@@ -52,6 +53,7 @@
}, },
"dependencies": { "dependencies": {
"pdfmake": "^0.2.20", "pdfmake": "^0.2.20",
"vue": "^3.5.30",
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
} }
} }

263
src/app-public/App.vue Normal file
View File

@@ -0,0 +1,263 @@
<template>
<div class="app-container">
<!-- Header -->
<header class="header">
<h1>📄 KSEF PDF Generator</h1>
<p class="subtitle">Generuj wizualizacje PDF faktur i UPO</p>
</header>
<!-- Navigation -->
<nav class="nav-tabs">
<button
@click="activeTab = 'invoice'"
:class="{ 'active': activeTab === 'invoice' }"
class="nav-btn"
>
📋 Faktura (FA1/FA2/FA3)
</button>
<button
@click="activeTab = 'upo'"
:class="{ 'active': activeTab === 'upo' }"
class="nav-btn"
>
📊 UPO v4.2
</button>
</nav>
<!-- Content -->
<main class="main-content">
<!-- Invoice Tab -->
<section v-if="activeTab === 'invoice'" class="tab-content">
<InvoiceGenerator
@pdf-generated="onPdfGenerated"
@error="onError"
/>
</section>
<!-- UPO Tab -->
<section v-if="activeTab === 'upo'" class="tab-content">
<UpoGenerator
@pdf-generated="onPdfGenerated"
@error="onError"
/>
</section>
<!-- Status Messages -->
<div v-if="message.text" :class="['message', message.type]">
{{ message.text }}
</div>
</main>
<!-- Footer -->
<footer class="footer">
<p>KSEF PDF Generator v1.0.0 | Oparte o Vite + Vue 3 + TypeScript</p>
<p class="copyright">
© 2026 | Oparte na bibliotece
<a href="https://github.com/CIRFMF/ksef-pdf-generator" target="_blank" rel="noopener noreferrer">
github.com/CIRFMF
</a>
</p>
</footer>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import InvoiceGenerator from './components/InvoiceGenerator.vue';
import UpoGenerator from './components/UpoGenerator.vue';
interface Message {
text: string;
type: 'success' | 'error' | 'info';
}
const activeTab = ref<'invoice' | 'upo'>('invoice');
const message = ref<Message>({ text: '', type: 'info' });
const onPdfGenerated = (fileName: string) => {
message.value = {
text: `✅ PDF "${fileName}" wygenerowany pomyślnie!`,
type: 'success',
};
setTimeout(() => {
message.value.text = '';
}, 5000);
};
const onError = (error: string) => {
message.value = {
text: `❌ Błąd: ${error}`,
type: 'error',
};
};
</script>
<style scoped>
.app-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
.header {
background: rgba(0, 0, 0, 0.2);
color: white;
padding: 2rem 1rem;
text-align: center;
backdrop-filter: blur(10px);
}
.header h1 {
font-size: 2.5rem;
margin: 0 0 0.5rem 0;
font-weight: 700;
}
.subtitle {
font-size: 1rem;
margin: 0;
opacity: 0.9;
}
.nav-tabs {
display: flex;
gap: 1rem;
padding: 1rem;
background: rgba(0, 0, 0, 0.1);
justify-content: center;
}
.nav-btn {
padding: 0.75rem 1.5rem;
border: 2px solid transparent;
border-radius: 8px;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.nav-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.nav-btn.active {
background: white;
color: #667eea;
border-color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.main-content {
flex: 1;
padding: 2rem 1rem;
max-width: 900px;
margin: 0 auto;
width: 100%;
}
.tab-content {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message {
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
font-weight: 500;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.message.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.footer {
background: rgba(0, 0, 0, 0.2);
color: white;
text-align: center;
padding: 1rem;
font-size: 0.9rem;
backdrop-filter: blur(10px);
}
.footer p {
margin: 0.25rem 0;
}
.copyright {
font-size: 0.85rem;
opacity: 0.9;
}
.copyright a {
color: #a0d0ff;
text-decoration: none;
border-bottom: 1px dashed #a0d0ff;
transition: all 0.3s ease;
}
.copyright a:hover {
color: white;
border-bottom-color: white;
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.75rem;
}
.nav-tabs {
flex-direction: column;
}
.nav-btn {
width: 100%;
}
.main-content {
padding: 1rem;
}
}
</style>

View File

@@ -0,0 +1,384 @@
<template>
<div class="generator-card">
<h2>Wygeneruj Fakturę PDF</h2>
<div class="upload-section">
<label for="invoiceFile" class="file-input-label">
<span class="upload-icon">📁</span>
<span v-if="!selectedFile" class="upload-text">
Kliknij aby wybrać plik XML
</span>
<span v-else class="file-name">
{{ selectedFile.name }}
</span>
</label>
<input
id="invoiceFile"
type="file"
accept=".xml"
@change="onFileSelected"
class="file-input-hidden"
/>
</div>
<div v-if="selectedFile" class="file-info">
<p class="file-size">Rozmiar: {{ fileSize }}</p>
<p class="file-type">Format: XML</p>
</div>
<div class="button-group">
<button
@click="generatePdf"
:disabled="!selectedFile || isLoading"
class="btn btn-primary"
>
<span v-if="!isLoading" class="btn-text">🚀 Wygeneruj PDF</span>
<span v-else class="btn-text"> Generowanie...</span>
</button>
<button
v-if="selectedFile"
@click="clearFile"
class="btn btn-secondary"
>
🗑 Wyczyść
</button>
</div>
<div v-if="progress" class="progress-section">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
<p class="progress-text">{{ progress }}%</p>
</div>
<div v-if="additionalData" class="data-preview">
<h3>Dane Dodatkowe</h3>
<div class="data-item">
<span class="data-label">Nr KSeF:</span>
<span class="data-value">{{ additionalData.nrKSeF }}</span>
</div>
<div class="data-item">
<span class="data-label">QR Code:</span>
<span class="data-value data-value-url">{{ additionalData.qrCode }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { generateInvoice } from '../../lib-public';
import type { AdditionalDataTypes } from '../../lib-public/types/common.types';
const emit = defineEmits<{
'pdf-generated': [filename: string];
'error': [error: string];
}>();
const selectedFile = ref<File | null>(null);
const isLoading = ref(false);
const progress = ref(0);
const additionalData: AdditionalDataTypes = {
nrKSeF: '5555555555-20250808-9231003CA67B-BE',
qrCode: 'https://ksef-test.mf.gov.pl/invoice/5265877635/26-10-2025/HS5E1zrA8WVjDNq_xMVIN5SD6nyRymmQ-BcYHReUAa0',
};
const fileSize = computed(() => {
if (!selectedFile.value) return '0 B';
const bytes = selectedFile.value.size;
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
});
const onFileSelected = (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file && file.type === 'text/xml') {
selectedFile.value = file;
} else {
emit('error', 'Wybierz prawidłowy plik XML');
selectedFile.value = null;
}
};
const clearFile = () => {
selectedFile.value = null;
progress.value = 0;
};
const generatePdf = async () => {
if (!selectedFile.value) {
emit('error', 'Brak wybranego pliku');
return;
}
try {
isLoading.value = true;
progress.value = 20;
// Symulacja postępu
const progressInterval = setInterval(() => {
progress.value = Math.min(progress.value + Math.random() * 30, 90);
}, 300);
// Generowanie PDF
const pdfBlob = await generateInvoice(
selectedFile.value,
additionalData,
'blob'
);
clearInterval(progressInterval);
progress.value = 100;
// Download PDF
const url = URL.createObjectURL(pdfBlob);
const link = document.createElement('a');
link.href = url;
link.download = `faktura-${Date.now()}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
emit('pdf-generated', link.download);
// Reset po 1 sekundzie
setTimeout(() => {
clearFile();
isLoading.value = false;
}, 1000);
} catch (error) {
emit('error', error instanceof Error ? error.message : 'Nieznanego błędu');
clearFile();
isLoading.value = false;
}
};
</script>
<style scoped>
.generator-card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.generator-card h2 {
margin: 0 0 1.5rem 0;
color: #333;
font-size: 1.5rem;
}
.upload-section {
margin-bottom: 1.5rem;
}
.file-input-label {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem;
border: 2px dashed #667eea;
border-radius: 8px;
background: #f8f9ff;
cursor: pointer;
transition: all 0.3s ease;
}
.file-input-label:hover {
background: #f0f2ff;
border-color: #764ba2;
}
.upload-icon {
font-size: 2rem;
}
.upload-text,
.file-name {
font-weight: 500;
color: #667eea;
}
.file-name {
color: #764ba2;
overflow: hidden;
text-overflow: ellipsis;
}
.file-input-hidden {
display: none;
}
.file-info {
display: flex;
gap: 1rem;
padding: 1rem;
background: #f0f2ff;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.file-info p {
margin: 0;
color: #667eea;
}
.file-size {
font-weight: 600;
}
.button-group {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.btn {
flex: 1;
min-width: 150px;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
transform: translateY(-2px);
}
.btn-text {
display: flex;
align-items: center;
gap: 0.5rem;
}
.progress-section {
margin-bottom: 1.5rem;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
font-size: 0.9rem;
color: #667eea;
font-weight: 600;
margin: 0;
}
.data-preview {
padding: 1rem;
background: #f8f9ff;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.data-preview h3 {
margin: 0 0 1rem 0;
color: #667eea;
font-size: 1rem;
}
.data-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
gap: 1rem;
}
.data-label {
font-weight: 600;
color: #667eea;
}
.data-value {
color: #666;
word-break: break-word;
}
.data-value-url {
font-size: 0.85rem;
opacity: 0.8;
}
@media (max-width: 768px) {
.generator-card {
padding: 1.5rem;
}
.button-group {
flex-direction: column;
}
.btn {
min-width: unset;
}
.file-info {
flex-direction: column;
}
}
</style>

View File

@@ -0,0 +1,382 @@
<template>
<div class="generator-card">
<h2>Wygeneruj UPO PDF</h2>
<div class="upload-section">
<label for="upoFile" class="file-input-label">
<span class="upload-icon">📁</span>
<span v-if="!selectedFile" class="upload-text">
Kliknij aby wybrać plik XML UPO v4.2
</span>
<span v-else class="file-name">
{{ selectedFile.name }}
</span>
</label>
<input
id="upoFile"
type="file"
accept=".xml"
@change="onFileSelected"
class="file-input-hidden"
/>
</div>
<div v-if="selectedFile" class="file-info">
<p class="file-size">Rozmiar: {{ fileSize }}</p>
<p class="file-type">Format: XML (UPO v4.2)</p>
</div>
<div class="button-group">
<button
@click="generatePdf"
:disabled="!selectedFile || isLoading"
class="btn btn-primary"
>
<span v-if="!isLoading" class="btn-text">🚀 Wygeneruj PDF</span>
<span v-else class="btn-text"> Generowanie...</span>
</button>
<button
v-if="selectedFile"
@click="clearFile"
class="btn btn-secondary"
>
🗑 Wyczyść
</button>
</div>
<div v-if="progress" class="progress-section">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
</div>
<p class="progress-text">{{ progress }}%</p>
</div>
<div class="info-box">
<h3> Informacje</h3>
<ul>
<li>Format: XML kompatybilny z UPO v4.2</li>
<li>Generuje wizualizację PDF dokumentu UPO</li>
<li>Obsługuje wszystkie warianty struktur</li>
<li>Maksymalny rozmiar: 100 MB</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { generatePDFUPO } from '../../lib-public';
const emit = defineEmits<{
'pdf-generated': [filename: string];
'error': [error: string];
}>();
const selectedFile = ref<File | null>(null);
const isLoading = ref(false);
const progress = ref(0);
const fileSize = computed(() => {
if (!selectedFile.value) return '0 B';
const bytes = selectedFile.value.size;
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
});
const onFileSelected = (event: Event) => {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file && file.type === 'text/xml') {
selectedFile.value = file;
} else {
emit('error', 'Wybierz prawidłowy plik XML');
selectedFile.value = null;
}
};
const clearFile = () => {
selectedFile.value = null;
progress.value = 0;
};
const generatePdf = async () => {
if (!selectedFile.value) {
emit('error', 'Brak wybranego pliku');
return;
}
try {
isLoading.value = true;
progress.value = 20;
// Symulacja postępu
const progressInterval = setInterval(() => {
progress.value = Math.min(progress.value + Math.random() * 30, 90);
}, 300);
// Generowanie PDF
const pdfBlob = await generatePDFUPO(
selectedFile.value,
'blob'
);
clearInterval(progressInterval);
progress.value = 100;
// Download PDF
const url = URL.createObjectURL(pdfBlob);
const link = document.createElement('a');
link.href = url;
link.download = `upo-${Date.now()}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
emit('pdf-generated', link.download);
// Reset po 1 sekundzie
setTimeout(() => {
clearFile();
isLoading.value = false;
}, 1000);
} catch (error) {
emit('error', error instanceof Error ? error.message : 'Nieznanego błędu');
clearFile();
isLoading.value = false;
}
};
</script>
<style scoped>
.generator-card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
animation: slideUp 0.5s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.generator-card h2 {
margin: 0 0 1.5rem 0;
color: #333;
font-size: 1.5rem;
}
.upload-section {
margin-bottom: 1.5rem;
}
.file-input-label {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem;
border: 2px dashed #667eea;
border-radius: 8px;
background: #f8f9ff;
cursor: pointer;
transition: all 0.3s ease;
}
.file-input-label:hover {
background: #f0f2ff;
border-color: #764ba2;
}
.upload-icon {
font-size: 2rem;
}
.upload-text,
.file-name {
font-weight: 500;
color: #667eea;
}
.file-name {
color: #764ba2;
overflow: hidden;
text-overflow: ellipsis;
}
.file-input-hidden {
display: none;
}
.file-info {
display: flex;
gap: 1rem;
padding: 1rem;
background: #f0f2ff;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.file-info p {
margin: 0;
color: #667eea;
}
.file-size {
font-weight: 600;
}
.button-group {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
.btn {
flex: 1;
min-width: 150px;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
transform: translateY(-2px);
}
.btn-text {
display: flex;
align-items: center;
gap: 0.5rem;
}
.progress-section {
margin-bottom: 1.5rem;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
}
.progress-text {
text-align: center;
font-size: 0.9rem;
color: #667eea;
font-weight: 600;
margin: 0;
}
.info-box {
padding: 1.5rem;
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
border-radius: 8px;
border-left: 4px solid #667eea;
}
.info-box h3 {
margin: 0 0 1rem 0;
color: #667eea;
font-size: 1rem;
}
.info-box ul {
margin: 0;
padding-left: 1.5rem;
list-style: none;
}
.info-box ul li {
color: #555;
margin-bottom: 0.5rem;
padding-left: 1.5rem;
position: relative;
}
.info-box ul li:before {
content: '✓';
position: absolute;
left: 0;
color: #667eea;
font-weight: bold;
}
@media (max-width: 768px) {
.generator-card {
padding: 1.5rem;
}
.button-group {
flex-direction: column;
}
.btn {
min-width: unset;
}
.file-info {
flex-direction: column;
}
.info-box ul {
padding-left: 1rem;
}
.info-box ul li {
padding-left: 1.5rem;
}
}
</style>

View File

@@ -1,29 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="pl">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>XML Parser</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KSEF PDF Generator - Vue 3</title>
</head> </head>
<body> <body>
<h1>📄 XML Parser</h1> <div id="app"></div>
<script type="module" src="./main.ts"></script>
<h2>Wygeneruj fakture:</h2>
<input
accept=".xml"
id="xmlInput"
type="file"
/>
<h2>Wygeneruj UPO:</h2>
<input
accept=".xml"
id="xmlInputUPO"
type="file"
/>
<script
src="./main.ts"
type="module"
></script>
</body> </body>
</html> </html>

View File

@@ -1,55 +1,6 @@
import { generateInvoice, generatePDFUPO } from '../lib-public'; import { createApp } from 'vue';
import App from './App.vue';
import { AdditionalDataTypes } from '../lib-public/types/common.types'; const app = createApp(App);
const inputInvoice: HTMLInputElement = document.getElementById('xmlInput') as HTMLInputElement; app.mount('#app');
const inputUPO: HTMLInputElement = document.getElementById('xmlInputUPO') as HTMLInputElement;
inputInvoice.addEventListener('change', async (): Promise<void> => {
const file: File | undefined = inputInvoice.files?.[0];
if (!file) {
return;
}
const additionalData: AdditionalDataTypes = {
nrKSeF: '5555555555-20250808-9231003CA67B-BE',
qrCode:
'https://ksef-test.mf.gov.pl/invoice/5265877635/26-10-2025/HS5E1zrA8WVjDNq_xMVIN5SD6nyRymmQ-BcYHReUAa0',
};
generateInvoice(file, additionalData, 'blob').then((data: Blob): void => {
const url: string = URL.createObjectURL(data);
const a: HTMLAnchorElement = document.createElement('a');
a.href = url;
a.download = 'test.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
});
inputUPO.addEventListener('change', async (): Promise<void> => {
const file: File | undefined = inputUPO.files?.[0];
if (!file) {
return;
}
generatePDFUPO(file).then((blob) => {
const url: string = URL.createObjectURL(blob);
const a: HTMLAnchorElement = document.createElement('a');
a.href = url;
a.download = 'test.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
});

View File

@@ -1,10 +1,13 @@
import path from 'path'; import path from 'path';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({ export default defineConfig({
root: path.resolve(__dirname, 'src/app-public'), root: path.resolve(__dirname, 'src/app-public'),
base: '/ksef/', base: '/ksef/',
plugins: [vue()],
build: { build: {
outDir: path.resolve(__dirname, 'dist-app'), outDir: path.resolve(__dirname, 'dist-app'),
emptyOutDir: true, emptyOutDir: true,