Popravka sidebara: kolaps, podmeni i HTMX navigacija

This commit is contained in:
2026-06-08 19:29:17 +02:00
parent f53618ce5e
commit a99920d102
32 changed files with 1385 additions and 400 deletions
+18 -2
View File
@@ -208,8 +208,22 @@ body {
/* accordion podmeni u sidebaru */
.nav-podmeni {
overflow: hidden;
max-height: 0;
transition: max-height 0.22s ease-out;
}
.nav-podmeni.otvoren {
max-height: 300px;
transition: max-height 0.25s ease-in;
}
/* pri inicijalnom učitavanju sprečavamo animaciju podmenia */
.nav-podmeni.bez-tranzicije {
transition: none !important;
}
/* u skupljenom modu podmeni se nikad ne prikazuje */
.sidebar.skupljen .nav-podmeni {
max-height: 0 !important;
transition: none;
}
/* u skupljenom modu alpine x-show kontroliše vidljivost — ne skrivamo display:none */
.nav-podstavka {
padding-left: 48px !important;
}
@@ -256,8 +270,10 @@ body {
display: none;
}
.nav-stavka:hover .nav-tooltip {
/* specifičniji selektor pobjeđuje .sidebar.skupljen .nav-stavka span { opacity:0 } */
.sidebar.skupljen .nav-stavka:hover .nav-tooltip {
opacity: 1;
pointer-events: none;
}
/* dno sidebara */
+51 -3
View File
@@ -1,3 +1,43 @@
// otvara/zatvara podmeni u sidebaru — funkcioniše i posle HTMX swap-a (čisti onclick, bez Alpine-a)
function ntechTogglePodmeni(btn) {
var sidebar = document.getElementById('sidebar');
// ako je sidebar skupljen, raširimo ga pre nego otvorimo podmeni
if (sidebar && sidebar.classList.contains('skupljen')) {
sidebar.classList.remove('skupljen');
localStorage.setItem('sidebar-skupljeno', 'false');
}
var podmeni = btn.nextElementSibling;
if (!podmeni) return;
podmeni.classList.toggle('otvoren');
var strelicaSvg = btn.querySelector('.nav-strelica svg');
if (strelicaSvg) {
strelicaSvg.style.transform = podmeni.classList.contains('otvoren') ? 'rotate(180deg)' : 'rotate(0deg)';
}
}
// registruje klik listenere na podmeni dugmad — sidebar se nikad ne menja, poziva se jednom
function ntechDodajPodmeniListenere() {
document.querySelectorAll('#sidebar [data-podmeni-dugme]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
ntechTogglePodmeni(btn);
});
});
}
// sprečava CSS tranziciju na podmeniima koji su već otvoreni pri inicijalnom učitavanju
// (bez ovoga, max-height animira od 0 do 300px pri svakom swap-u)
function ntechInicijalizujPodmeni() {
document.querySelectorAll('.nav-podmeni.otvoren').forEach(function(el) {
el.classList.add('bez-tranzicije');
requestAnimationFrame(function() {
requestAnimationFrame(function() {
el.classList.remove('bez-tranzicije');
});
});
});
}
document.addEventListener('alpine:init', () => {
// sidebar — podmeni "Moj profil"
@@ -82,7 +122,7 @@ document.addEventListener('alpine:init', () => {
// forma za prodaju
Alpine.data('prodajaForma', () => ({
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
stavke: [{artikal_id: '', kolicina: 1, cena: 0, pdv_stopa: 20}],
artikliOpcije: [],
isMobile: false,
init() {
@@ -93,14 +133,17 @@ document.addEventListener('alpine:init', () => {
})
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0})
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0, pdv_stopa: 20})
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1)
},
popuniCenu(stavka) {
const a = this.artikliOpcije.find(x => x.id == stavka.artikal_id)
if (a) stavka.cena = a.cena
if (a) {
stavka.cena = a.cena
stavka.pdv_stopa = a.pdv_stopa !== undefined ? a.pdv_stopa : 20
}
},
dostupnaKolicina(i) {
const stavka = this.stavke[i]
@@ -202,3 +245,8 @@ document.addEventListener('alpine:init', () => {
}))
})
// za prvo učitavanje (defer script) — sprečava animaciju podmenia koji su inicijalno otvoreni
ntechInicijalizujPodmeni()
// dodaje klik listenere na podmeni dugmad (data-podmeni-dugme)
ntechDodajPodmeniListenere()
+36 -14
View File
@@ -24,7 +24,7 @@
</div>
</div>
<nav class="sidebar-nav" aria-label="Glavna navigacija" hx-boost="true" hx-target="body" hx-swap="innerHTML">
<nav class="sidebar-nav" aria-label="Glavna navigacija" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
<div class="nav-oznaka">Glavni meni</div>
<a href="/dashboard" class="nav-stavka {{if eq .Stranica "dashboard"}}aktivan{{end}}">
@@ -89,21 +89,21 @@
<div class="nav-oznaka">Nalog</div>
{{if index .Dozvole "tema.lokalno"}}
<div x-data="sidebarProfil" data-otvoren="{{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}true{{else}}false{{end}}">
<button type="button" @click="otvoren = !otvoren"
<div>
<button type="button" data-podmeni-dugme
class="nav-stavka {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Moj profil</span>
<span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
:style="'transition:transform 0.2s;transform:' + (otvoren ? 'rotate(180deg)' : 'rotate(0deg)')">
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}rotate(180deg){{else}}rotate(0deg){{end}}">
<polyline points="6 9 12 15 18 9"/>
</svg>
</span>
<span class="nav-tooltip">Moj profil</span>
</button>
<div class="nav-podmeni" x-show="otvoren" x-transition>
<div class="nav-podmeni {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}otvoren{{end}}">
<a href="/admin/profil" class="nav-stavka nav-podstavka {{if eq .Stranica "profil"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Opšte</span>
@@ -137,32 +137,32 @@
<div class="nav-oznaka">Sistem</div>
{{if index .Dozvole "podesavanja.pregled"}}
<div x-data="sidebarPodesavanja" data-otvoren="{{if or (eq .Stranica "podesavanja") (eq .Stranica "dozvole")}}true{{else}}false{{end}}">
<button type="button" @click="otvoren = !otvoren"
class="nav-stavka {{if eq .Stranica "podesavanja"}}aktivan{{end}}"
<div>
<button type="button" data-podmeni-dugme
class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>Podešavanja</span>
<span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
:style="'transition:transform 0.2s;transform:' + (otvoren ? 'rotate(180deg)' : 'rotate(0deg)')">
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}">
<polyline points="6 9 12 15 18 9"/>
</svg>
</span>
<span class="nav-tooltip">Podešavanja</span>
</button>
<div class="nav-podmeni" x-show="otvoren" x-transition>
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka">
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}otvoren{{end}}">
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
<span>Opšte</span>
<span class="nav-tooltip">Opšte</span>
</a>
<a href="/admin/podesavanja/izgled" class="nav-stavka nav-podstavka">
<a href="/admin/podesavanja/izgled" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-izgled"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21a9 9 0 1 1 0-18c4.97 0 9 3.582 9 8 0 1.06-.474 2.078-1.318 2.828S17.626 15 16.5 15H15a2 2 0 0 0-2 2 2 2 0 0 0 2 2h.5"/><circle cx="8.5" cy="10.5" r=".5"/><circle cx="12" cy="7.5" r=".5"/><circle cx="15.5" cy="10.5" r=".5"/></svg>
<span>Izgled</span>
<span class="nav-tooltip">Izgled</span>
</a>
<a href="/admin/podesavanja/sistem" class="nav-stavka nav-podstavka">
<a href="/admin/podesavanja/sistem" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-sistem"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="8" rx="1"/><rect x="2" y="13" width="20" height="8" rx="1"/><path d="M6 7h.01M6 17h.01"/></svg>
<span>Sistem</span>
<span class="nav-tooltip">Sistem</span>
@@ -181,11 +181,33 @@
</nav>
<div class="sidebar-dno">
<a href="/odjava" class="nav-stavka">
<a href="/odjava" class="nav-stavka" hx-boost="false">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
<span>Odjava</span>
<span class="nav-tooltip">Odjava</span>
</a>
</div>
</aside>
<script>
(function() {
var sidebar = document.getElementById('sidebar');
if (!sidebar) return;
sidebar.querySelectorAll('[data-podmeni-dugme]').forEach(function(btn) {
btn.addEventListener('click', function(e) {
e.stopPropagation();
if (sidebar.classList.contains('skupljen')) {
sidebar.classList.remove('skupljen');
localStorage.setItem('sidebar-skupljen', 'false');
}
var podmeni = btn.nextElementSibling;
if (!podmeni) return;
podmeni.classList.toggle('otvoren');
var svg = btn.querySelector('.nav-strelica svg');
if (svg) {
svg.style.transform = podmeni.classList.contains('otvoren') ? 'rotate(180deg)' : 'rotate(0deg)';
}
});
});
})();
</script>
{{end}}
+21 -8
View File
@@ -41,8 +41,10 @@
(function() {
var dugme = document.getElementById('avatar-dugme');
var meni = document.getElementById('avatar-meni');
if (!dugme || !meni) return;
// premesti meni na kraj body — izlazi iz stacking contexta topbara
// ukloni stari meni iz body-ja (ostao od prethodne stranice) i dodaj novi
document.querySelectorAll('body > #avatar-meni').forEach(function(el) { el.remove(); });
document.body.appendChild(meni);
function pozicioniraj() {
@@ -52,6 +54,7 @@
meni.style.left = 'auto';
}
// dugme dobija novi listener svaki put (novi element pri svakoj navigaciji)
dugme.addEventListener('click', function(e) {
e.stopPropagation();
if (meni.style.display === 'none' || meni.style.display === '') {
@@ -63,13 +66,23 @@
}
});
document.addEventListener('click', function() {
meni.style.display = 'none';
});
window.addEventListener('resize', function() {
if (meni.style.display !== 'none') pozicioniraj();
});
// document i window listeneri — samo jednom, ne smeju da se gomilaju
if (!window._ntechAvatarDodato) {
window._ntechAvatarDodato = true;
document.addEventListener('click', function() {
var m = document.getElementById('avatar-meni');
if (m) m.style.display = 'none';
});
window.addEventListener('resize', function() {
var m = document.getElementById('avatar-meni');
var d = document.getElementById('avatar-dugme');
if (m && d && m.style.display !== 'none') {
var r = d.getBoundingClientRect();
m.style.top = (r.bottom + 8) + 'px';
m.style.right = (window.innerWidth - r.right) + 'px';
}
});
}
})();
</script>
{{end}}
+14 -3
View File
@@ -3,7 +3,6 @@
{{define "naslov"}}Izveštaji — NTech{{end}}
{{define "dodatni-css"}}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
<style>
.izv-naslov { font-size: 13px; font-weight: 500; color: var(--tekst-sporedni); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 14px; }
.toggle-red { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
@@ -194,11 +193,13 @@
(function() {
var podaci = {{.GrafikonJSON}};
var isDark = document.documentElement.getAttribute('data-tema') !== 'svetla';
function inicijalizujGrafikon() {
var tekstBoja = getComputedStyle(document.documentElement).getPropertyValue('--tekst-sporedni').trim() || '#6b7280';
var ivicaBoja = getComputedStyle(document.documentElement).getPropertyValue('--ivica').trim() || '#e5e7eb';
var ctx = document.getElementById('grafikon-prihod').getContext('2d');
var canvas = document.getElementById('grafikon-prihod');
if (!canvas) return;
var ctx = canvas.getContext('2d');
var grafikon = new Chart(ctx, {
type: 'bar',
data: {
@@ -256,6 +257,16 @@
document.getElementById('toggle-grafikon').addEventListener('change', function() {
document.getElementById('grafikon-wrapper').style.display = this.checked ? 'block' : 'none';
});
} // kraj inicijalizujGrafikon
if (window.Chart) {
inicijalizujGrafikon();
} else {
var s = document.createElement('script');
s.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js';
s.onload = inicijalizujGrafikon;
document.head.appendChild(s);
}
})();
</script>
{{end}}
+74 -33
View File
@@ -5,13 +5,23 @@
{{define "dodatni-css"}}
<style>
.greska-animacija { animation: shake 0.4s ease; }
.tip-radio { display:flex; gap:12px; }
.tip-radio label {
display:flex; align-items:center; gap:6px;
cursor:pointer; font-size:14px; color:var(--tekst-glavni);
padding:8px 14px; border-radius:6px; border:1px solid var(--ivica);
transition:background 0.15s, border-color 0.15s;
}
.tip-radio input[type=radio] { accent-color: var(--akcentna); }
.tip-radio label:has(input:checked) {
background:var(--akcentna-bleda); border-color:var(--akcentna);
}
</style>
{{end}}
{{define "sadrzaj"}}
<div style="width:100%;">
<!-- nazad dugme -->
<a href="/klijenti" class="nazad-link">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>
Nazad na klijente
@@ -29,67 +39,84 @@
{{end}}
<form method="POST" action="{{if .Izmena}}/klijenti/izmeni/{{.Klijent.ID}}{{else}}/klijenti/novi{{end}}">
<div style="display:flex;flex-direction:column;gap:14px;">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<div style="display:flex;flex-direction:column;gap:18px;">
<!-- ime i prezime -->
<!-- tip klijenta -->
<div>
<div style="font-size:12px;font-weight:500;color:var(--tekst-sporedni);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:10px;">
Fizičko lice
<div class="sekcija-naslov">Tip klijenta</div>
<div class="tip-radio">
<label>
<input type="radio" name="tip" value="fizicko"
{{if or (eq .Klijent.Tip "fizicko") (eq .Klijent.Tip "")}}checked{{end}}
onchange="promenaTipa(this.value)">
Fizičko lice
</label>
<label>
<input type="radio" name="tip" value="pravno"
{{if eq .Klijent.Tip "pravno"}}checked{{end}}
onchange="promenaTipa(this.value)">
Pravno lice
</label>
</div>
</div>
<!-- sekcija fizičko lice -->
<div id="sec-fizicko" style="display:flex;flex-direction:column;gap:12px;">
<div class="sekcija-naslov">Ime i prezime</div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Ime</label>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">
Ime <span id="ime-oznaka" style="color:#dc2626;">*</span>
</label>
<input type="text" name="ime" value="{{.Klijent.Ime}}"
placeholder="npr. Marko"
style="width:100%;">
placeholder="npr. Marko" style="width:100%;">
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Prezime</label>
<input type="text" name="prezime" value="{{.Klijent.Prezime}}"
placeholder="npr. Petrović"
style="width:100%;">
placeholder="npr. Petrović" style="width:100%;">
</div>
</div>
<div style="max-width:260px;">
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">JMBG</label>
<input type="text" name="jmbg" value="{{.Klijent.JMBG}}"
placeholder="13 cifara" maxlength="13" style="width:100%;">
</div>
</div>
<!-- naziv firme i pib -->
<div>
<div style="font-size:12px;font-weight:500;color:var(--tekst-sporedni);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:10px;">
Firma
</div>
<!-- sekcija pravno lice -->
<div id="sec-pravno" style="display:none;flex-direction:column;gap:12px;">
<div class="sekcija-naslov">Firma</div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Naziv firme</label>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">
Naziv firme <span style="color:#dc2626;">*</span>
</label>
<input type="text" name="naziv_firme" value="{{.Klijent.NazivFirme}}"
placeholder="npr. TechSolutions d.o.o."
style="width:100%;">
placeholder="npr. TechSolutions d.o.o." style="width:100%;">
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">PIB</label>
<input type="text" name="pib" value="{{.Klijent.PIB}}"
placeholder="npr. 123456789"
style="width:100%;">
placeholder="npr. 123456789" style="width:100%;">
</div>
</div>
</div>
<!-- telefon i e-pošta -->
<!-- kontakt -->
<div>
<div style="font-size:12px;font-weight:500;color:var(--tekst-sporedni);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:10px;">
Kontakt
</div>
<div class="sekcija-naslov">Kontakt</div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Telefon</label>
<input type="text" name="telefon" value="{{.Klijent.Telefon}}"
placeholder="npr. 060 123 4567"
style="width:100%;">
placeholder="npr. 060 123 4567" style="width:100%;">
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">E-pošta</label>
<input type="text" name="email" value="{{.Klijent.Email}}"
placeholder="npr. marko@example.com"
style="width:100%;">
placeholder="npr. marko@example.com" style="width:100%;">
</div>
</div>
</div>
@@ -102,11 +129,6 @@
style="width:100%;resize:vertical;">{{.Klijent.Napomena}}</textarea>
</div>
<!-- obaveštenje o obaveznim poljima -->
<div style="font-size:12px;color:var(--tekst-sporedni);">
Mora biti uneto ime i prezime <em>ili</em> naziv firme.
</div>
<!-- dugmad -->
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:6px;">
<a href="/klijenti" class="btn-sekundarno">Odustani</a>
@@ -119,4 +141,23 @@
</form>
</div>
</div>
<script>
function promenaTipa(tip) {
var secFiz = document.getElementById('sec-fizicko');
var secPrav = document.getElementById('sec-pravno');
if (tip === 'pravno') {
secFiz.style.display = 'none';
secPrav.style.display = 'flex';
} else {
secFiz.style.display = 'flex';
secPrav.style.display = 'none';
}
}
// pri učitavanju prikazujemo ispravnu sekciju
(function() {
var checked = document.querySelector('input[name="tip"]:checked');
if (checked) promenaTipa(checked.value);
})();
</script>
{{end}}
+77 -110
View File
@@ -43,15 +43,16 @@
padding-bottom: 14px;
border-bottom: 0.5px solid var(--ivica);
">
<span
style="
font-size: 20px;
font-weight: 600;
color: var(--tekst-glavni);
font-family: monospace;
">
{{.Nalog.BrojNaloga}}
</span>
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
<span style="font-size:20px;font-weight:600;color:var(--tekst-glavni);font-family:monospace;">
{{.Nalog.BrojNaloga}}
</span>
{{if .Nalog.Stornirano}}
<span style="display:inline-block;padding:3px 10px;border-radius:20px;font-size:12px;font-weight:500;background:rgba(220,38,38,0.12);color:#dc2626;">
Stornirano
</span>
{{end}}
</div>
<a
href="/prodaja/{{.Nalog.ID}}/stampa"
target="_blank"
@@ -110,18 +111,26 @@
</div>
</div>
<div>
<div
style="
font-size: 12px;
color: var(--tekst-sporedni);
margin-bottom: 4px;
">
Ukupno
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Način plaćanja</div>
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">
{{if eq .Nalog.NacinPlacanja "gotovina"}}Gotovina
{{else if eq .Nalog.NacinPlacanja "kartica"}}Platna kartica
{{else if eq .Nalog.NacinPlacanja "prenos"}}Bankarski prenos
{{else}}—{{end}}
</div>
<div style="font-size: 20px; font-weight: 600; color: var(--sb-akcent)">
</div>
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ukupno</div>
<div style="font-size:20px;font-weight:600;color:var(--sb-akcent);">
{{printf "%.2f" .Nalog.Ukupno}} din
</div>
</div>
{{if .Nalog.RazlogStorniranja}}
<div style="grid-column:1/-1;">
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Razlog storniranja</div>
<div style="font-size:13px;color:#dc2626;">{{.Nalog.RazlogStorniranja}}</div>
</div>
{{end}}
{{if .Nalog.Napomena}}
<div style="grid-column: 1/-1">
<div
@@ -184,15 +193,10 @@
">
Cena/kom
</th>
<th
style="
padding: 10px 20px;
text-align: right;
font-size: 12px;
font-weight: 500;
color: var(--tekst-sporedni);
width: 120px;
">
<th style="padding:10px 20px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:80px;">
PDV %
</th>
<th style="padding:10px 20px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:120px;">
Ukupno
</th>
</tr>
@@ -200,105 +204,68 @@
<tbody>
{{range .Stavke}}
<tr style="border-bottom: 0.5px solid var(--ivica)">
<td
style="
padding: 10px 20px;
font-size: 14px;
color: var(--tekst-glavni);
">
{{.ArtikalNaziv}}
</td>
<td
style="
padding: 10px 20px;
text-align: center;
font-size: 14px;
color: var(--tekst-glavni);
">
{{.Kolicina}}
</td>
<td
style="
padding: 10px 20px;
text-align: right;
font-size: 14px;
color: var(--tekst-sporedni);
">
{{printf "%.2f" .CenaPoKomadu}} din
</td>
<td
style="
padding: 10px 20px;
text-align: right;
font-size: 14px;
font-weight: 500;
color: var(--tekst-glavni);
">
{{printf "%.2f" .Ukupno}} din
<td style="padding:10px 20px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
<td style="padding:10px 20px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
<td style="padding:10px 20px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .CenaPoKomadu}} din</td>
<td style="padding:10px 20px;text-align:center;font-size:13px;color:var(--tekst-sporedni);">
{{if .PdvStopa}}{{printf "%.0f" .PdvStopa}}%{{else}}—{{end}}
</td>
<td style="padding:10px 20px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
</tr>
{{end}}
</tbody>
<tfoot>
<tr style="border-top: 0.5px solid var(--ivica)">
<td
colspan="3"
style="
padding: 12px 20px;
text-align: right;
font-size: 13px;
font-weight: 500;
color: var(--tekst-sporedni);
">
Ukupno:
</td>
<td
style="
padding: 12px 20px;
text-align: right;
font-size: 16px;
font-weight: 600;
color: var(--sb-akcent);
">
{{printf "%.2f" .Nalog.Ukupno}} din
</td>
<td colspan="4" style="padding:12px 20px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno:</td>
<td style="padding:12px 20px;text-align:right;font-size:16px;font-weight:600;color:var(--sb-akcent);">{{printf "%.2f" .Nalog.Ukupno}} din</td>
</tr>
</tfoot>
</table>
</div>
</div>
{{if index $.Dozvole "prodaja.obrisi"}}
<!-- zona za brisanje -->
<div class="kartica detalji-kartica animiraj" style="border-color: #dc262633">
<div
style="display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap">
<div style="flex: 1; min-width: 200px">
<div
style="
font-size: 14px;
font-weight: 500;
color: #dc2626;
margin-bottom: 4px;
">
Brisanje naloga
</div>
<div style="font-size: 13px; color: var(--tekst-sporedni)">
Brisanje je trajno. Količine artikala biće vraćene na stanje u
magacinu.
<!-- storno i brisanje -->
<div class="kartica detalji-kartica animiraj" style="border-color:#dc262633;">
<div style="display:flex;flex-direction:column;gap:16px;">
{{if and (index $.Dozvole "prodaja.storno") (not .Nalog.Stornirano) }}
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
<div style="flex:1;min-width:200px;">
<div style="font-size:14px;font-weight:500;color:#f97316;margin-bottom:4px;">Storniranje naloga</div>
<div style="font-size:13px;color:var(--tekst-sporedni);">
Storno vraća sve artikle na stanje u magacinu. Nalog ostaje u evidenciji sa oznakom storniran.
</div>
</div>
<form method="POST" action="/prodaja/storno/{{.Nalog.ID}}" style="display:flex;flex-direction:column;gap:8px;align-items:flex-end;">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<input type="text" name="razlog" placeholder="Razlog storniranja (opciono)" style="width:240px;">
<button type="submit" class="btn-primarno" style="background:#f97316;"
data-potvrda="Stornirati nalog {{.Nalog.BrojNaloga}}?">
Storniraj nalog
</button>
</form>
</div>
<form method="POST" action="/prodaja/obrisi/{{.Nalog.ID}}">
<button
type="submit"
class="btn-primarno"
style="background: #dc2626"
data-potvrda="Da li ste sigurni da želite da obrišete nalog {{.Nalog.BrojNaloga}}? Količine artikala biće vraćene na stanje.">
Obriši nalog
</button>
</form>
{{end}}
{{if index $.Dozvole "prodaja.obrisi"}}
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;padding-top:14px;border-top:0.5px solid var(--ivica);">
<div style="flex:1;min-width:200px;">
<div style="font-size:14px;font-weight:500;color:#dc2626;margin-bottom:4px;">Brisanje naloga</div>
<div style="font-size:13px;color:var(--tekst-sporedni);">
Brisanje je trajno.{{if not .Nalog.Stornirano}} Količine artikala biće vraćene na stanje u magacinu.{{end}}
</div>
</div>
<form method="POST" action="/prodaja/obrisi/{{.Nalog.ID}}">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<button type="submit" class="btn-primarno" style="background:#dc2626;"
data-potvrda="Da li ste sigurni da želite da obrišete nalog {{.Nalog.BrojNaloga}}?">
Obriši nalog
</button>
</form>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
+22 -27
View File
@@ -49,35 +49,28 @@
style="font-size: 16px; font-weight: 500; color: var(--tekst-glavni)">Nova prodaja</span>
</div>
<div style="display: flex; flex-direction: column; gap: 14px">
<div>
<label
style="
font-size: 13px;
color: var(--tekst-sporedni);
display: block;
margin-bottom: 6px;
">Klijent</label>
<select name="klijent_id" style="width: 100%">
<option value="">— bez klijenta —</option>
{{range .Klijenti}}
<option value="{{.ID}}">
{{if .NazivFirme}}{{.NazivFirme}}{{else}}{{.Ime}}
{{.Prezime}}{{end}}
</option>
{{end}}
</select>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Klijent</label>
<select name="klijent_id" style="width: 100%">
<option value="">— bez klijenta —</option>
{{range .Klijenti}}
<option value="{{.ID}}">{{.PunoIme}}</option>
{{end}}
</select>
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Način plaćanja</label>
<select name="nacin_placanja" style="width:100%;">
<option value="gotovina">Gotovina</option>
<option value="kartica">Platna kartica</option>
<option value="prenos">Bankarski prenos</option>
</select>
</div>
</div>
<div>
<label
style="
font-size: 13px;
color: var(--tekst-sporedni);
display: block;
margin-bottom: 6px;
">Napomena</label>
<textarea
name="napomena"
rows="2"
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Napomena</label>
<textarea name="napomena" rows="2"
placeholder="Interna napomena o prodaji..."
style="width: 100%; resize: vertical"></textarea>
</div>
@@ -163,6 +156,7 @@
<template x-for="(stavka, i) in stavke" :key="i">
<tr style="border-bottom: 0.5px solid var(--ivica)">
<td style="padding: 8px 10px">
<input type="hidden" :name="'pdv_stopa[]'" :value="stavka.pdv_stopa">
<select
:name="'artikal_id[]'"
x-model="stavka.artikal_id"
@@ -322,6 +316,7 @@
</button>
</div>
<div style="display: flex; flex-direction: column; gap: 10px">
<input type="hidden" :name="'pdv_stopa[]'" :value="stavka.pdv_stopa">
<div>
<label
style="
+86 -5
View File
@@ -68,12 +68,24 @@
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.DatumZavrsetka.Format "02.01.2006."}}</div>
</div>
{{end}}
{{if .Nalog.GarancijaDo}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Garancija do</div>
<div style="font-size:14px;color:#16a34a;font-weight:500;">{{.Nalog.GarancijaDo.Format "02.01.2006."}}</div>
</div>
{{end}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Klijent</div>
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">
{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}—{{end}}
</div>
</div>
{{if .TehnicarNaziv}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Tehničar</div>
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{.TehnicarNaziv}}</div>
</div>
{{end}}
</div>
</div>
@@ -149,12 +161,81 @@
</div>
</div>
<!-- placeholder za buduće funkcije -->
<div class="kartica detalji-kartica animiraj" style="border-style:dashed;">
<div style="display:flex;align-items:center;gap:10px;color:var(--tekst-sporedni);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<span style="font-size:13px;">Istorija promena statusa i vezani artikli biće dostupni u narednoj verziji.</span>
<!-- ugrađeni delovi -->
<div class="kartica detalji-kartica animiraj">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);flex-wrap:wrap;gap:10px;">
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Ugrađeni delovi</span>
</div>
<!-- forma za dodavanje dela -->
{{if index .Dozvole "servis.izmeni"}}
<form method="POST" action="/servis/{{.Nalog.ID}}/delovi" style="margin-bottom:16px;">
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
<div style="display:grid;grid-template-columns:1fr 80px 120px auto;gap:10px;align-items:flex-end;">
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
<select name="artikal_id" style="width:100%;" required>
<option value="">— odaberi —</option>
{{range .Artikli}}
<option value="{{.ID}}">{{.Naziv}} ({{.Kolicina}} kom)</option>
{{end}}
</select>
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Kol.</label>
<input type="number" name="kolicina" min="1" value="1" style="width:100%;" required>
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
<input type="number" name="cena_komada" min="0" step="0.01" value="0" style="width:100%;" required>
</div>
<div>
<button type="submit" class="btn-primarno" style="font-size:13px;padding:8px 14px;white-space:nowrap;">+ Dodaj</button>
</div>
</div>
</form>
{{end}}
<!-- lista delova -->
{{if .ServisniDelovi}}
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr style="border-bottom:0.5px solid var(--ivica);">
<th style="padding:8px 10px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Artikal</th>
<th style="padding:8px 10px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:80px;">Kol.</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Cena/kom</th>
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:130px;">Ukupno</th>
<th style="width:50px;"></th>
</tr>
</thead>
<tbody>
{{range .ServisniDelovi}}
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 10px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
<td style="padding:9px 10px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
<td style="padding:9px 10px;text-align:right;font-size:14px;color:var(--tekst-glavni);">{{printf "%.2f" .CenaKomada}} din</td>
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
<td style="padding:9px 10px;text-align:center;">
{{if index $.Dozvole "servis.izmeni"}}
<form method="POST" action="/servis/{{$.Nalog.ID}}/delovi/{{.ID}}/obrisi" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{$.CsrfToken}}">
<button type="submit"
data-potvrda="Ukloniti deo '{{.ArtikalNaziv}}'?"
style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:16px;padding:2px 6px;border-radius:4px;">
×
</button>
</form>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<div style="font-size:13px;color:var(--tekst-sporedni);text-align:center;padding:16px 0;">
Nema ugrađenih delova za ovaj nalog.
</div>
{{end}}
</div>
<!-- zona za brisanje -->
+39 -16
View File
@@ -70,25 +70,42 @@
</div>
</div>
<!-- klijent -->
<!-- klijent i tehničar -->
<div>
<div class="sekcija-naslov">Klijent</div>
<select name="klijent_id" style="width:100%;">
<option value="">— bez klijenta —</option>
{{range .Klijenti}}
<option value="{{.ID}}"
{{if eq .ID $.Nalog.KlijentIDVrednost}}selected{{end}}>
{{if .NazivFirme}}{{.NazivFirme}}{{else}}{{.Ime}} {{.Prezime}}{{end}}
</option>
{{end}}
</select>
</div>
<!-- status i datum završetka -->
<div>
<div class="sekcija-naslov">Status</div>
<div class="sekcija-naslov">Klijent i tehničar</div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Klijent</label>
<select name="klijent_id" style="width:100%;">
<option value="">— bez klijenta —</option>
{{range .Klijenti}}
<option value="{{.ID}}"
{{if eq .ID $.Nalog.KlijentIDVrednost}}selected{{end}}>
{{.PunoIme}}
</option>
{{end}}
</select>
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Tehničar</label>
<select name="tehnicar_id" style="width:100%;">
<option value="">— nedodeljen —</option>
{{range .Tehnicari}}
<option value="{{.ID}}"
{{if eq .ID $.Nalog.TehnicarIDVrednost}}selected{{end}}>
{{.KorisnickoIme}}
</option>
{{end}}
</select>
</div>
</div>
</div>
<!-- status i datumi -->
<div>
<div class="sekcija-naslov">Status i datumi</div>
<div class="forma-grid-4" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;">
<div style="grid-column:span 2;">
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Status naloga</label>
<select name="status" style="width:100%;">
{{range .SviStatusi}}
@@ -102,6 +119,12 @@
value="{{if .Nalog.DatumZavrsetka}}{{.Nalog.DatumZavrsetka.Format "2006-01-02"}}{{end}}"
style="width:100%;">
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Garancija do</label>
<input type="date" name="garancija_do"
value="{{if .Nalog.GarancijaDo}}{{.Nalog.GarancijaDo.Format "2006-01-02"}}{{end}}"
style="width:100%;">
</div>
</div>
</div>
+70 -42
View File
@@ -81,32 +81,32 @@
<div class="sidebar-overlay" id="sidebar-overlay"></div>
{{template "sidebar" .}}
<div class="glavni-sadrzaj">
<div class="glavni-sadrzaj" id="glavni-sadrzaj">
{{template "topbar" .}}
<main class="sadrzaj">{{block "sadrzaj" .}}{{end}}</main>
<main class="sadrzaj" id="sadrzaj">
{{if .Flash}}
<div id="flash-toast" class="flash-toast flash-{{.Flash.Tip}}" role="alert">
<span class="flash-ikona">{{if eq .Flash.Tip "uspeh"}}✓{{else}}!{{end}}</span>
<span class="flash-tekst">{{.Flash.Poruka}}</span>
<button class="flash-zatvori" onclick="this.parentElement.remove()" aria-label="Zatvori">×</button>
</div>
<script>
(function() {
var t = document.getElementById('flash-toast');
if (!t) return;
setTimeout(function() {
t.classList.add('flash-izlaz');
setTimeout(function() { if (t.parentElement) t.remove(); }, 350);
}, 4000);
})();
</script>
{{end}}
{{block "sadrzaj" .}}{{end}}
</main>
</div>
</div>
<!-- flash poruka — prikazuje se kao toast u gornjem desnom uglu -->
{{if .Flash}}
<div id="flash-toast" class="flash-toast flash-{{.Flash.Tip}}" role="alert">
<span class="flash-ikona">{{if eq .Flash.Tip "uspeh"}}✓{{else}}!{{end}}</span>
<span class="flash-tekst">{{.Flash.Poruka}}</span>
<button class="flash-zatvori" onclick="this.parentElement.remove()" aria-label="Zatvori">×</button>
</div>
<script>
(function() {
var t = document.getElementById('flash-toast');
if (!t) return;
setTimeout(function() {
t.classList.add('flash-izlaz');
setTimeout(function() { if (t.parentElement) t.remove(); }, 350);
}, 4000);
})();
</script>
{{end}}
<!-- alpine.js komponente (mora biti pre Alpine-a) -->
<script src="/static/js/ntech.js" defer></script>
<!-- alpine.js CSP build (lokalno, bez unsafe-eval) -->
@@ -115,22 +115,27 @@
<!-- htmx za komunikaciju sa serverom -->
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
<!-- sidebar logika -->
<!-- sidebar logika — sidebar se NIKAD ne menja pri HTMX navigaciji, pa nema potrebe za swap logikom -->
<script>
(function() {
if (window._ntechSidebarDodato) return;
window._ntechSidebarDodato = true;
function mobilni() { return window.innerWidth <= 768; }
function inicijalizujSidebar() {
var sidebar = document.getElementById("sidebar");
var hamburger = document.getElementById("hamburger");
var overlay = document.getElementById("sidebar-overlay");
if (!sidebar || !hamburger || !overlay) return;
var sidebar = document.getElementById("sidebar");
var hamburger = document.getElementById("hamburger");
var overlay = document.getElementById("sidebar-overlay");
if (!mobilni() && localStorage.getItem("sidebar-skupljen") === "true") {
sidebar.classList.add("skupljen");
}
document.documentElement.classList.remove('sidebar-init-skupljen');
// skupljeno stanje pri učitavanju stranice
if (sidebar && !mobilni() && localStorage.getItem("sidebar-skupljen") === "true") {
sidebar.classList.add("skupljen");
}
document.documentElement.classList.remove('sidebar-init-skupljen');
if (typeof ntechInicijalizujPodmeni === 'function') ntechInicijalizujPodmeni();
// hamburger — sidebar element živi ceo vek stranice, listener se dodaje jednom
if (hamburger && sidebar && overlay) {
hamburger.addEventListener("click", function() {
if (mobilni()) {
sidebar.classList.toggle("otvoren");
@@ -140,27 +145,50 @@
localStorage.setItem("sidebar-skupljen", sidebar.classList.contains("skupljen"));
}
});
overlay.addEventListener("click", function() {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
});
}
// resize listener se dodaje samo jednom — ne sme da se gomila po swap-ovima
if (!window._ntechResizeDodato) {
window._ntechResizeDodato = true;
window.addEventListener("resize", function() {
var sidebar = document.getElementById("sidebar");
var overlay = document.getElementById("sidebar-overlay");
if (sidebar && overlay && !mobilni()) {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
window.addEventListener("resize", function() {
if (!mobilni() && sidebar && overlay) {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
}
});
// aktivna stavka — ažurira se pri svakoj HTMX navigaciji
function azurirajAktivne() {
var putanja = window.location.pathname;
document.querySelectorAll('#sidebar .nav-stavka').forEach(function(el) {
el.classList.remove('aktivan');
});
document.querySelectorAll('#sidebar .nav-podmeni').forEach(function(el) {
el.classList.remove('otvoren');
var strelica = el.previousElementSibling && el.previousElementSibling.querySelector('.nav-strelica svg');
if (strelica) strelica.style.transform = 'rotate(0deg)';
});
document.querySelectorAll('#sidebar a.nav-stavka').forEach(function(el) {
var href = el.getAttribute('href');
if (!href) return;
if (putanja === href || putanja.startsWith(href + '/')) {
el.classList.add('aktivan');
var podmeni = el.closest('.nav-podmeni');
if (podmeni) {
podmeni.classList.add('otvoren');
var roditelj = podmeni.previousElementSibling;
if (roditelj) {
roditelj.classList.add('aktivan');
var str = roditelj.querySelector('.nav-strelica svg');
if (str) str.style.transform = 'rotate(180deg)';
}
}
}
});
}
inicijalizujSidebar();
document.addEventListener('htmx:afterSwap', azurirajAktivne);
})();
</script>