Complete Next.js 16 application with 13 fully implemented modules: Email Signature, Word XML Generator, Registratura, Dashboard, Tag Manager, IT Inventory, Address Book, Password Vault, Mini Utilities, Prompt Generator, Digital Signatures, Word Templates, and AI Chat. Includes core platform systems (module registry, feature flags, storage abstraction, i18n, theming, auth stub, tagging), 16 technical documentation files, Docker deployment config, and legacy HTML tool reference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
695 lines
21 KiB
HTML
695 lines
21 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ro">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Beletage – Word XML Data Engine</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<!-- JSZip pentru arhivă ZIP -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"
|
||
integrity="sha512-FGv7V3GpCr3C6wz6Q4z8F1v8y4mZohwPqhwKiPfz0btvAvOE0tfLOgvBcFQncn1C3KW0y5fN9c7v1sQW8vGfMQ=="
|
||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||
<style>
|
||
:root {
|
||
color-scheme: dark;
|
||
}
|
||
body {
|
||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||
margin: 0;
|
||
padding: 1.5rem;
|
||
background: #020617;
|
||
color: #e5e7eb;
|
||
}
|
||
h1 {
|
||
font-size: 1.7rem;
|
||
margin-bottom: .25rem;
|
||
}
|
||
.subtitle {
|
||
font-size: .9rem;
|
||
color: #9ca3af;
|
||
margin-bottom: 1rem;
|
||
}
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
.card {
|
||
background: #020617;
|
||
border-radius: 1rem;
|
||
padding: 1.1rem 1.3rem;
|
||
margin-bottom: 1rem;
|
||
border: 1px solid #1e293b;
|
||
box-shadow: 0 15px 35px rgba(0,0,0,.45);
|
||
}
|
||
label {
|
||
font-size: .8rem;
|
||
color: #9ca3af;
|
||
display: block;
|
||
margin-bottom: .2rem;
|
||
}
|
||
input, textarea, select {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: .5rem .6rem;
|
||
border-radius: .5rem;
|
||
border: 1px solid #334155;
|
||
background: #020617;
|
||
color: #e5e7eb;
|
||
font-family: inherit;
|
||
font-size: .9rem;
|
||
outline: none;
|
||
}
|
||
input:focus, textarea:focus, select:focus {
|
||
border-color: #38bdf8;
|
||
box-shadow: 0 0 0 1px #38bdf8;
|
||
}
|
||
textarea { min-height: 140px; resize: vertical; }
|
||
.row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1rem;
|
||
}
|
||
.col-3 { flex: 1 1 220px; }
|
||
.col-6 { flex: 1 1 320px; }
|
||
.col-9 { flex: 3 1 420px; }
|
||
|
||
button {
|
||
padding: .55rem 1.1rem;
|
||
border-radius: 999px;
|
||
border: none;
|
||
background: linear-gradient(135deg, #38bdf8, #6366f1);
|
||
color: #fff;
|
||
font-size: .9rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
box-shadow: 0 12px 25px rgba(37,99,235,.4);
|
||
}
|
||
button:hover { filter: brightness(1.05); transform: translateY(-1px); }
|
||
button:active { transform: translateY(0); box-shadow: 0 8px 18px rgba(37,99,235,.6); }
|
||
|
||
.btn-secondary {
|
||
background: transparent;
|
||
border: 1px solid #4b5563;
|
||
box-shadow: none;
|
||
}
|
||
.btn-secondary:hover {
|
||
background: #020617;
|
||
box-shadow: 0 8px 20px rgba(0,0,0,.6);
|
||
}
|
||
.btn-small {
|
||
font-size: .8rem;
|
||
padding: .35rem .8rem;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.toggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .4rem;
|
||
font-size: .8rem;
|
||
color: #cbd5f5;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.toggle input { width: auto; }
|
||
|
||
.pill-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: .4rem;
|
||
margin-bottom: .4rem;
|
||
}
|
||
.pill {
|
||
padding: .25rem .7rem;
|
||
border-radius: 999px;
|
||
border: 1px solid #334155;
|
||
font-size: .8rem;
|
||
cursor: pointer;
|
||
background: #020617;
|
||
color: #e5e7eb;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: .35rem;
|
||
}
|
||
.pill.active {
|
||
background: linear-gradient(135deg, #38bdf8, #6366f1);
|
||
border-color: transparent;
|
||
color: #0f172a;
|
||
}
|
||
.pill span.remove {
|
||
font-size: .8rem;
|
||
opacity: .7;
|
||
}
|
||
.pill span.remove:hover { opacity: 1; }
|
||
|
||
.small {
|
||
font-size: .8rem;
|
||
color: #9ca3af;
|
||
margin-top: .25rem;
|
||
}
|
||
|
||
pre {
|
||
background: #020617;
|
||
border-radius: .75rem;
|
||
padding: .7rem .8rem;
|
||
border: 1px solid #1f2937;
|
||
overflow: auto;
|
||
font-size: .8rem;
|
||
max-height: 340px;
|
||
}
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: .15rem .45rem;
|
||
border-radius: 999px;
|
||
font-size: .7rem;
|
||
background: rgba(148,163,184,.18);
|
||
margin-right: .4rem;
|
||
margin-bottom: .25rem;
|
||
}
|
||
@media (max-width: 768px) {
|
||
body { padding: 1rem; }
|
||
.card { padding: 1rem; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>Beletage – Word XML Data Engine</h1>
|
||
<p class="subtitle">
|
||
Generator de <strong>Custom XML Parts</strong> pentru Word, pe categorii (Beneficiar, Proiect, Suprafete, Meta etc.),
|
||
cu mod <em>Simple</em> / <em>Advanced</em> și câmpuri derivate (Short, Upper, Initials) + POT/CUT pregătite.
|
||
</p>
|
||
|
||
<!-- SETĂRI GLOBALE -->
|
||
<div class="card">
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label for="baseNs">Bază Namespace (se completează automat cu /Categorie)</label>
|
||
<input id="baseNs" type="text" value="http://schemas.beletage.ro/contract">
|
||
<div class="small">Ex: <code>http://schemas.beletage.ro/contract</code> → pentru categoria „Proiect” devine
|
||
<code>http://schemas.beletage.ro/contract/Proiect</code>.
|
||
</div>
|
||
</div>
|
||
<div class="col-3">
|
||
<label>Mod generare câmpuri</label>
|
||
<div class="pill-row">
|
||
<div class="pill active" id="modeSimplePill" onclick="setMode('simple')">Simple</div>
|
||
<div class="pill" id="modeAdvancedPill" onclick="setMode('advanced')">Advanced</div>
|
||
</div>
|
||
<div class="small">
|
||
<strong>Simple</strong>: doar câmpurile tale.<br>
|
||
<strong>Advanced</strong>: + Short / Upper / Lower / Initials / First pentru fiecare câmp.
|
||
</div>
|
||
</div>
|
||
<div class="col-3">
|
||
<label>Opțiuni extra</label>
|
||
<div class="small" style="margin-top:.25rem;">
|
||
<label class="toggle">
|
||
<input type="checkbox" id="computeMetrics" checked>
|
||
<span>Adaugă câmpuri POT / CUT în categoria Suprafete</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CATEGORII -->
|
||
<div class="card">
|
||
<div class="row">
|
||
<div class="col-3">
|
||
<label>Categorii de date</label>
|
||
<div id="categoryPills" class="pill-row"></div>
|
||
<button class="btn-secondary btn-small" onclick="addCategoryPrompt()">+ Adaugă categorie</button>
|
||
<div class="small">
|
||
Exemple de organizare: <code>Beneficiar</code>, <code>Proiect</code>, <code>Suprafete</code>, <code>Meta</code>.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-9">
|
||
<label>Câmpuri pentru categoria selectată</label>
|
||
<textarea id="fieldsArea"></textarea>
|
||
<div class="small">
|
||
Un câmp pe linie. Poți edita lista. Butonul „Reset categorie la preset” reîncarcă valorile default pentru
|
||
categoria curentă (dacă există).
|
||
</div>
|
||
<div style="margin-top:.5rem; display:flex; gap:.5rem; flex-wrap:wrap;">
|
||
<button class="btn-secondary btn-small" onclick="resetCategoryToPreset()">Reset categorie la preset</button>
|
||
<button class="btn-secondary btn-small" onclick="clearCategoryFields()">Curăță câmpurile</button>
|
||
</div>
|
||
<div class="small" id="nsRootInfo" style="margin-top:.6rem;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- GENERARE & DOWNLOAD -->
|
||
<div class="card">
|
||
<div style="display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; margin-bottom:.5rem;">
|
||
<button onclick="generateAll()">Generează XML pentru toate categoriile</button>
|
||
<button class="btn-secondary" onclick="downloadCurrentXml()">Descarcă XML categorie curentă</button>
|
||
<button class="btn-secondary" onclick="downloadZipAll()">Descarcă ZIP cu toate XML-urile</button>
|
||
</div>
|
||
<div class="small">
|
||
<span class="badge">Tip</span>
|
||
În Word, fiecare fișier generat devine un Custom XML Part separat (ex: <code>BeneficiarData.xml</code>,
|
||
<code>ProiectData.xml</code> etc.), perfect pentru organizarea mapping-urilor.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PREVIEW -->
|
||
<div class="card">
|
||
<h3 style="margin-top:0;">Preview XML & XPaths</h3>
|
||
<div class="small" style="margin-bottom:.4rem;">
|
||
Selectează o categorie pentru a vedea XML-ul și XPaths-urile aferente.
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<div class="badge">XML categorie curentă</div>
|
||
<pre id="xmlPreview"></pre>
|
||
</div>
|
||
<div class="col-6">
|
||
<div class="badge">XPaths categorie curentă</div>
|
||
<pre id="xpathPreview"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// --- PRESETURI CATEGORII ---
|
||
const defaultPresets = {
|
||
"Beneficiar": [
|
||
"NumeClient",
|
||
"Adresa",
|
||
"CUI",
|
||
"CNP",
|
||
"Reprezentant",
|
||
"Email",
|
||
"Telefon"
|
||
],
|
||
"Proiect": [
|
||
"TitluProiect",
|
||
"AdresaImobil",
|
||
"NrCadastral",
|
||
"NrCF",
|
||
"Localitate",
|
||
"Judet"
|
||
],
|
||
"Suprafete": [
|
||
"SuprafataTeren",
|
||
"SuprafataConstruitaLaSol",
|
||
"SuprafataDesfasurata",
|
||
"SuprafataUtila"
|
||
],
|
||
"Meta": [
|
||
"NrContract",
|
||
"DataContract",
|
||
"Responsabil",
|
||
"VersiuneDocument",
|
||
"DataGenerarii"
|
||
]
|
||
};
|
||
|
||
// --- STATE ---
|
||
let categories = {}; // { Categorie: { fieldsText: "..." } }
|
||
let currentCategory = "Beneficiar";
|
||
let mode = "advanced"; // "simple" | "advanced"
|
||
const xmlParts = {}; // { Categorie: xmlString }
|
||
const xpathParts = {}; // { Categorie: xpathString }
|
||
|
||
// --- UTILITARE ---
|
||
function sanitizeName(name) {
|
||
if (!name) return null;
|
||
let n = name.trim();
|
||
if (!n) return null;
|
||
n = n.replace(/\s+/g, "_").replace(/[^A-Za-z0-9_.-]/g, "");
|
||
if (!/^[A-Za-z_]/.test(n)) n = "_" + n;
|
||
return n;
|
||
}
|
||
|
||
function initialsFromLabel(label) {
|
||
if (!label) return "";
|
||
return label.trim().split(/\s+/).map(s => s.charAt(0).toUpperCase() + ".").join("");
|
||
}
|
||
|
||
function firstToken(label) {
|
||
if (!label) return "";
|
||
return label.trim().split(/\s+/)[0] || "";
|
||
}
|
||
|
||
function getBaseNamespace() {
|
||
const val = document.getElementById("baseNs").value.trim();
|
||
return val || "http://schemas.beletage.ro/contract";
|
||
}
|
||
|
||
function getCategoryNamespace(cat) {
|
||
const base = getBaseNamespace();
|
||
const safeCat = sanitizeName(cat) || cat;
|
||
return base.replace(/\/+$/,"") + "/" + safeCat;
|
||
}
|
||
|
||
function getCategoryRoot(cat) {
|
||
const safeCat = sanitizeName(cat) || cat;
|
||
return safeCat + "Data";
|
||
}
|
||
|
||
// --- MOD SIMPLE/ADVANCED ---
|
||
function setMode(m) {
|
||
mode = m === "advanced" ? "advanced" : "simple";
|
||
document.getElementById("modeSimplePill").classList.toggle("active", mode === "simple");
|
||
document.getElementById("modeAdvancedPill").classList.toggle("active", mode === "advanced");
|
||
// regenerăm previw dacă avem ceva
|
||
generateAll(false);
|
||
}
|
||
|
||
// --- CATEGORII: INIT, UI, STORAGE ---
|
||
function initCategories() {
|
||
// încarcă din localStorage, altfel default
|
||
const saved = window.localStorage.getItem("beletage_xml_categories");
|
||
if (saved) {
|
||
try {
|
||
const parsed = JSON.parse(saved);
|
||
categories = parsed.categories || {};
|
||
currentCategory = parsed.currentCategory || "Beneficiar";
|
||
} catch(e) {
|
||
Object.keys(defaultPresets).forEach(cat => {
|
||
categories[cat] = { fieldsText: defaultPresets[cat].join("\n") };
|
||
});
|
||
currentCategory = "Beneficiar";
|
||
}
|
||
} else {
|
||
Object.keys(defaultPresets).forEach(cat => {
|
||
categories[cat] = { fieldsText: defaultPresets[cat].join("\n") };
|
||
});
|
||
currentCategory = "Beneficiar";
|
||
}
|
||
|
||
renderCategoryPills();
|
||
loadCategoryToUI(currentCategory);
|
||
}
|
||
|
||
function persistCategories() {
|
||
try {
|
||
window.localStorage.setItem("beletage_xml_categories", JSON.stringify({
|
||
categories,
|
||
currentCategory
|
||
}));
|
||
} catch(e){}
|
||
}
|
||
|
||
function renderCategoryPills() {
|
||
const container = document.getElementById("categoryPills");
|
||
container.innerHTML = "";
|
||
Object.keys(categories).forEach(cat => {
|
||
const pill = document.createElement("div");
|
||
pill.className = "pill" + (cat === currentCategory ? " active" : "");
|
||
pill.onclick = () => switchCategory(cat);
|
||
pill.textContent = cat;
|
||
|
||
// nu permitem ștergerea preset-urilor de bază direct (doar la custom)
|
||
if (!defaultPresets[cat]) {
|
||
const remove = document.createElement("span");
|
||
remove.className = "remove";
|
||
remove.textContent = "×";
|
||
remove.onclick = (ev) => {
|
||
ev.stopPropagation();
|
||
deleteCategory(cat);
|
||
};
|
||
pill.appendChild(remove);
|
||
}
|
||
container.appendChild(pill);
|
||
});
|
||
}
|
||
|
||
function switchCategory(cat) {
|
||
saveCurrentCategoryFields();
|
||
currentCategory = cat;
|
||
renderCategoryPills();
|
||
loadCategoryToUI(cat);
|
||
updateNsRootInfo();
|
||
showPreview(cat);
|
||
persistCategories();
|
||
}
|
||
|
||
function loadCategoryToUI(cat) {
|
||
const area = document.getElementById("fieldsArea");
|
||
area.value = categories[cat]?.fieldsText || "";
|
||
updateNsRootInfo();
|
||
}
|
||
|
||
function saveCurrentCategoryFields() {
|
||
const area = document.getElementById("fieldsArea");
|
||
if (!categories[currentCategory]) {
|
||
categories[currentCategory] = { fieldsText: "" };
|
||
}
|
||
categories[currentCategory].fieldsText = area.value;
|
||
}
|
||
|
||
function deleteCategory(cat) {
|
||
if (!confirm(`Sigur ștergi categoria "${cat}"?`)) return;
|
||
delete categories[cat];
|
||
const keys = Object.keys(categories);
|
||
currentCategory = keys[0] || "Beneficiar";
|
||
renderCategoryPills();
|
||
loadCategoryToUI(currentCategory);
|
||
updateNsRootInfo();
|
||
persistCategories();
|
||
}
|
||
|
||
function addCategoryPrompt() {
|
||
const name = prompt("Nume categorie nouă (ex: Urbanism, Fiscal, Altele):");
|
||
if (!name) return;
|
||
const trimmed = name.trim();
|
||
if (!trimmed) return;
|
||
if (categories[trimmed]) {
|
||
alert("Categoria există deja.");
|
||
return;
|
||
}
|
||
categories[trimmed] = { fieldsText: "" };
|
||
currentCategory = trimmed;
|
||
renderCategoryPills();
|
||
loadCategoryToUI(currentCategory);
|
||
updateNsRootInfo();
|
||
persistCategories();
|
||
}
|
||
|
||
function resetCategoryToPreset() {
|
||
if (!defaultPresets[currentCategory]) {
|
||
alert("Categoria curentă nu are preset definit.");
|
||
return;
|
||
}
|
||
if (!confirm("Resetezi lista de câmpuri la presetul standard pentru această categorie?")) return;
|
||
categories[currentCategory].fieldsText = defaultPresets[currentCategory].join("\n");
|
||
loadCategoryToUI(currentCategory);
|
||
persistCategories();
|
||
}
|
||
|
||
function clearCategoryFields() {
|
||
categories[currentCategory].fieldsText = "";
|
||
loadCategoryToUI(currentCategory);
|
||
persistCategories();
|
||
}
|
||
|
||
function updateNsRootInfo() {
|
||
const ns = getCategoryNamespace(currentCategory);
|
||
const root = getCategoryRoot(currentCategory);
|
||
document.getElementById("nsRootInfo").innerHTML =
|
||
`<strong>Namespace:</strong> <code>${ns}</code><br>` +
|
||
`<strong>Root element:</strong> <code><${root}></code>`;
|
||
}
|
||
|
||
// --- GENERARE XML PENTRU O CATEGORIE ---
|
||
function generateCategory(cat) {
|
||
const entry = categories[cat];
|
||
if (!entry) return { xml: "", xpaths: "" };
|
||
|
||
const raw = (entry.fieldsText || "").split(/\r?\n/)
|
||
.map(l => l.trim())
|
||
.filter(l => l.length > 0);
|
||
|
||
if (raw.length === 0) {
|
||
return { xml: "", xpaths: "" };
|
||
}
|
||
|
||
const ns = getCategoryNamespace(cat);
|
||
const root = getCategoryRoot(cat);
|
||
const computeMetrics = document.getElementById("computeMetrics").checked;
|
||
|
||
const usedNames = new Set();
|
||
const fields = []; // { label, baseName, variants: [] }
|
||
|
||
for (const label of raw) {
|
||
const base = sanitizeName(label);
|
||
if (!base) continue;
|
||
|
||
let baseName = base;
|
||
let idx = 2;
|
||
while (usedNames.has(baseName)) {
|
||
baseName = base + "_" + idx;
|
||
idx++;
|
||
}
|
||
usedNames.add(baseName);
|
||
|
||
const variants = [baseName];
|
||
if (mode === "advanced") {
|
||
const advCandidates = [
|
||
baseName + "Short",
|
||
baseName + "Upper",
|
||
baseName + "Lower",
|
||
baseName + "Initials",
|
||
baseName + "First"
|
||
];
|
||
for (let v of advCandidates) {
|
||
let vn = v;
|
||
let k = 2;
|
||
while (usedNames.has(vn)) {
|
||
vn = v + "_" + k;
|
||
k++;
|
||
}
|
||
usedNames.add(vn);
|
||
variants.push(vn);
|
||
}
|
||
}
|
||
|
||
fields.push({ label, baseName, variants });
|
||
}
|
||
|
||
// detectăm câmpuri pentru metrici (în special categoria Suprafete)
|
||
const extraMetricFields = [];
|
||
if (computeMetrics && cat.toLowerCase().includes("suprafete")) {
|
||
const hasTeren = fields.some(f => f.baseName.toLowerCase().includes("suprafatateren"));
|
||
const hasLaSol = fields.some(f => f.baseName.toLowerCase().includes("suprafataconstruitalasol"));
|
||
const hasDesf = fields.some(f => f.baseName.toLowerCase().includes("suprafatadesfasurata"));
|
||
|
||
if (hasTeren && hasLaSol) {
|
||
if (!usedNames.has("POT")) {
|
||
usedNames.add("POT");
|
||
extraMetricFields.push({ label: "Procent Ocupare Teren", baseName: "POT", variants: ["POT"] });
|
||
}
|
||
}
|
||
if (hasTeren && hasDesf) {
|
||
if (!usedNames.has("CUT")) {
|
||
usedNames.add("CUT");
|
||
extraMetricFields.push({ label: "Coeficient Utilizare Teren", baseName: "CUT", variants: ["CUT"] });
|
||
}
|
||
}
|
||
}
|
||
|
||
// generăm XML
|
||
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||
xml += `<${root} xmlns="${ns}">\n`;
|
||
|
||
const allFieldEntries = fields.concat(extraMetricFields);
|
||
|
||
for (const f of allFieldEntries) {
|
||
for (const v of f.variants) {
|
||
xml += ` <${v}></${v}>\n`;
|
||
}
|
||
}
|
||
|
||
xml += `</${root}>\n`;
|
||
|
||
// generăm XPaths
|
||
let xp = `Categorie: ${cat}\nNamespace: ${ns}\nRoot: /${root}\n\n`;
|
||
for (const f of fields) {
|
||
xp += `# ${f.label}\n`;
|
||
for (const v of f.variants) {
|
||
xp += `/${root}/${v}\n`;
|
||
}
|
||
xp += `\n`;
|
||
}
|
||
if (extraMetricFields.length > 0) {
|
||
xp += `# Metrici auto (POT / CUT)\n`;
|
||
for (const f of extraMetricFields) {
|
||
for (const v of f.variants) {
|
||
xp += `/${root}/${v}\n`;
|
||
}
|
||
}
|
||
xp += `\n`;
|
||
}
|
||
|
||
return { xml, xpaths: xp };
|
||
}
|
||
|
||
// --- GENERARE PENTRU TOATE CATEGORIILE ---
|
||
function generateAll(showForCurrent = true) {
|
||
saveCurrentCategoryFields();
|
||
Object.keys(categories).forEach(cat => {
|
||
const { xml, xpaths } = generateCategory(cat);
|
||
xmlParts[cat] = xml;
|
||
xpathParts[cat] = xpaths;
|
||
});
|
||
if (showForCurrent) {
|
||
showPreview(currentCategory);
|
||
}
|
||
persistCategories();
|
||
}
|
||
|
||
// --- PREVIEW ---
|
||
function showPreview(cat) {
|
||
document.getElementById("xmlPreview").textContent = xmlParts[cat] || "<!-- Niciun XML generat încă pentru această categorie. -->";
|
||
document.getElementById("xpathPreview").textContent = xpathParts[cat] || "";
|
||
}
|
||
|
||
// --- DOWNLOAD: XML CATEGORIE ---
|
||
function downloadCurrentXml() {
|
||
generateAll(false);
|
||
const xml = xmlParts[currentCategory];
|
||
if (!xml) {
|
||
alert("Nu există XML generat pentru categoria curentă. Apasă întâi „Generează XML pentru toate categoriile”.");
|
||
return;
|
||
}
|
||
const root = getCategoryRoot(currentCategory);
|
||
const fileName = root + ".xml";
|
||
const blob = new Blob([xml], { type: "application/xml" });
|
||
const a = document.createElement("a");
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = fileName;
|
||
a.click();
|
||
URL.revokeObjectURL(a.href);
|
||
}
|
||
|
||
// --- DOWNLOAD: ZIP CU TOATE XML-URILE ---
|
||
async function downloadZipAll() {
|
||
generateAll(false);
|
||
const cats = Object.keys(categories);
|
||
if (cats.length === 0) {
|
||
alert("Nu există categorii.");
|
||
return;
|
||
}
|
||
|
||
const zip = new JSZip();
|
||
const folder = zip.folder("customXmlParts");
|
||
|
||
let hasAny = false;
|
||
for (const cat of cats) {
|
||
const xml = xmlParts[cat];
|
||
if (!xml) continue;
|
||
hasAny = true;
|
||
const root = getCategoryRoot(cat);
|
||
const fileName = root + ".xml";
|
||
folder.file(fileName, xml);
|
||
}
|
||
|
||
if (!hasAny) {
|
||
alert("Nu există XML generat încă. Apasă întâi „Generează XML pentru toate categoriile”.");
|
||
return;
|
||
}
|
||
|
||
const content = await zip.generateAsync({ type: "blob" });
|
||
const a = document.createElement("a");
|
||
a.href = URL.createObjectURL(content);
|
||
a.download = "beletage_custom_xml_parts.zip";
|
||
a.click();
|
||
URL.revokeObjectURL(a.href);
|
||
}
|
||
|
||
// --- INIT ---
|
||
window.addEventListener("DOMContentLoaded", () => {
|
||
initCategories();
|
||
updateNsRootInfo();
|
||
generateAll();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|