Servis: custom dropdown za usluge i artikle (naziv+cena), live preračun na javnoj stranici

This commit is contained in:
2026-06-25 02:45:30 +02:00
parent 47ae5e9347
commit d9eeec4c6a
2 changed files with 163 additions and 36 deletions
+127 -32
View File
@@ -11,6 +11,35 @@
.detalji-kartica:nth-child(5) { animation-delay: 0.36s; }
/* bez animacija na detaljima — smetaju pri dodavanju/brisanju stavki */
#sadrzaj .kartica.animiraj { animation: none; }
/* custom dropdown za usluge i artikle */
.cust-sel { position: relative; }
.cust-sel-btn {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
height: 38px; padding: 0 10px; box-sizing: border-box; cursor: pointer;
border: 0.5px solid var(--ivica) !important; border-radius: 8px; font-size: 14px;
background: var(--kartica) !important; color: var(--tekst-glavni) !important;
user-select: none;
}
.cust-sel-btn:focus { border-color: #3b82f6 !important; outline: none; }
.cust-sel-tekst { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cust-sel-chevron { font-size: 10px; color: var(--tekst-sporedni); flex-shrink: 0; transition: transform 0.15s; }
.cust-sel.otvoren .cust-sel-chevron { transform: rotate(180deg); }
.cust-sel-lista {
display: none; position: absolute; top: calc(100% + 3px); left: 0; right: 0; z-index: 500;
background: var(--kartica); border: 0.5px solid var(--ivica); border-radius: 10px;
max-height: 260px; overflow-y: auto; box-shadow: 0 8px 30px rgba(0,0,0,0.4);
}
.cust-sel.otvoren .cust-sel-lista { display: block; }
.cso {
display: flex; align-items: center; justify-content: space-between; gap: 12px;
padding: 8px 12px; cursor: pointer; border-bottom: 0.5px solid var(--ivica); font-size: 13px;
}
.cso:last-child { border-bottom: none; }
.cso:hover, .cso.izabran { background: rgba(255,255,255,0.06); }
.cso-naziv { color: var(--tekst-glavni); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cso-cena { color: #f59e0b; font-weight: 600; white-space: nowrap; flex-shrink: 0; }
.cso-prazno { color: var(--tekst-sporedni); }
.kartica.cust-aktivan { position: relative; z-index: 100; }
</style>
{{end}}
@@ -467,10 +496,17 @@
<div class="detalji-forma-rad">
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Usluga</label>
<select name="usluga_id" id="rad-usluga" style="width:100%;" required>
<option value="">— odaberi uslugu —</option>
{{range .Usluge}}{{if not (index $.KorisceneUsluge .ID)}}<option value="{{.ID}}" data-cena="{{.Cena}}">{{.Naziv}} — {{dinari .Cena}} din</option>{{end}}{{end}}
</select>
<div class="cust-sel" data-sel="rad-usluga">
<div class="cust-sel-btn" tabindex="0">
<span class="cust-sel-tekst cso-prazno">— odaberi uslugu —</span>
<span class="cust-sel-chevron"></span>
</div>
<div class="cust-sel-lista">
<div class="cso" data-val="" data-cena="0"><span class="cso-prazno">— odaberi uslugu —</span></div>
{{range .Usluge}}{{if not (index $.KorisceneUsluge .ID)}}<div class="cso" data-val="{{.ID}}" data-cena="{{.Cena}}"><span class="cso-naziv">{{.Naziv}}</span><span class="cso-cena">{{dinari .Cena}} din</span></div>{{end}}{{end}}
</div>
</div>
<input type="hidden" name="usluga_id" id="rad-usluga">
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Kol.</label>
@@ -581,9 +617,14 @@
<div class="detalji-forma-deo">
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
<select name="artikal_id" id="deo-artikal" style="width:100%;" required>
<option value="">— odaberi artikal —</option>
</select>
<div class="cust-sel" id="deo-artikal-cust" data-sel="deo-artikal">
<div class="cust-sel-btn" tabindex="0">
<span class="cust-sel-tekst cso-prazno">— odaberi artikal —</span>
<span class="cust-sel-chevron"></span>
</div>
<div class="cust-sel-lista"></div>
</div>
<input type="hidden" name="artikal_id" id="deo-artikal">
</div>
<div>
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">U magacinu</label>
@@ -692,27 +733,30 @@
</div>
</div>
<script>(function(){
window._dinFmt=function(v){var s=Math.round((v||0)*100);var dec=String(s%100).padStart(2,'0');var cel=String(Math.floor(s/100));var r='';for(var i=0;i<cel.length;i++){if(i>0&&(cel.length-i)%3===0)r+='.';r+=cel[i];}return r+','+dec;}
var k=document.getElementById('deo-kategorija');
var a=document.getElementById('deo-artikal');
var u=document.getElementById('deo-umagacinu');
var l=document.getElementById('deo-kol');
var c=document.getElementById('deo-cena');
var st=0;
function osv(){if(!l||!a)return;var o=a.options[a.selectedIndex];var s=o?parseInt(o.getAttribute('data-stanje'),10):0;st=isNaN(s)?0:s;if(u)u.textContent=a.value?st:'—';if(l)l.classList.toggle('prekoraceno',a.value&&st===0)}
function osv(){if(!l||!a)return;var s=parseInt(a.getAttribute('data-stanje'),10);var st=isNaN(s)?0:s;if(u)u.textContent=a.value?st:'\u2014';if(l)l.classList.toggle('prekoraceno',!!a.value&&st===0)}
function pop(){
if(!a)return;
var cust=document.getElementById('deo-artikal-cust');
if(!cust)return;
var lista=cust.querySelector('.cust-sel-lista');
var ka=k?k.value:'';
a.innerHTML='<option value="">— odaberi artikal —</option>';
var aa=window._ntechArtikli||[];
aa.forEach(function(x){if(!ka||(x.KategorijaNaziv||'')===ka)a.innerHTML+='<option value="'+x.ID+'" data-cena="'+x.ProdajnaCena+'" data-stanje="'+(x.Kolicina||0)+'">'+x.Naziv+' — '+parseFloat(x.ProdajnaCena||0).toFixed(2)+' din</option>'})
}
if(k){var aa2=window._ntechArtikli||[];var kgs=[];aa2.forEach(function(x){var n=x.KategorijaNaziv||'';if(n&&kgs.indexOf(n)===-1)kgs.push(n)})
k.innerHTML='<option value="">— sve kategorije —</option>'+kgs.map(function(x){return'<option value="'+x+'">'+x+'</option>'}).join('');k.onchange=pop}
var aa=window._ntechArtikli||[];
var html='<div class="cso" data-val="" data-cena="0" data-stanje="0"><span class="cso-prazno">\u2014 odaberi artikal \u2014</span></div>';
aa.forEach(function(x){if(!ka||(x.KategorijaNaziv||'')===ka)html+='<div class="cso" data-val="'+x.ID+'" data-cena="'+x.ProdajnaCena+'" data-stanje="'+(x.Kolicina||0)+'"><span class="cso-naziv">'+x.Naziv+'</span><span class="cso-cena">'+window._dinFmt(x.ProdajnaCena)+' din</span></div>';});
if(lista)lista.innerHTML=html;
}
if(k){var aa2=window._ntechArtikli||[];var kgs=[];aa2.forEach(function(x){var n=x.KategorijaNaziv||'';if(n&&kgs.indexOf(n)===-1)kgs.push(n)})
k.innerHTML='<option value="">\u2014 sve kategorije \u2014</option>'+kgs.map(function(x){return'<option value="'+x+'">'+x+'</option>'}).join('');k.onchange=pop}
pop();
if(a)a.onchange=function(){var o=a.options[a.selectedIndex];if(!o)return;var v=parseFloat(o.getAttribute('data-cena'));if(c)c.value=(!isNaN(v)&&v>0)?v.toFixed(2):'0';osv()}
if(a)a.addEventListener('change',function(){var v=parseFloat(a.getAttribute('data-cena'));if(c)c.value=(!isNaN(v)&&v>0)?v.toFixed(2):'0';osv()});
if(l)l.oninput=osv;
var fd=document.querySelector('form[action$=\"/delovi\"]');
if(fd)fd.addEventListener('reset',function(){if(u){u.textContent='';u.classList.remove('prekoraceno')}st=0;if(l)l.classList.remove('prekoraceno')})
if(fd)fd.addEventListener('reset',function(){if(u){u.textContent='\u2014';u.classList.remove('prekoraceno')}if(l)l.classList.remove('prekoraceno')})
})();</script>
</div>
{{end}}
@@ -811,15 +855,63 @@
</div>
<script>
// Radovi: pri izboru usluge iz <select>-a popuni predloženu cenu (data-cena).
// Delegacija na document — kartica radova se osvežava AJAX-om (data-refresh),
// pa listener vezan za sam <select> ne bi preživeo zamenu elementa.
// Custom dropdown: toggle, izbor opcije, zatvaranje klikom van.
function _custZatvoriSve() {
document.querySelectorAll('.cust-sel.otvoren').forEach(function (el) {
el.classList.remove('otvoren');
var k = el.closest('.kartica');
if (k) k.classList.remove('cust-aktivan');
});
}
document.addEventListener('click', function (e) {
var btn = e.target.closest('.cust-sel-btn');
if (btn) {
var cust = btn.closest('.cust-sel');
var bio = cust.classList.contains('otvoren');
_custZatvoriSve();
if (!bio) {
cust.classList.add('otvoren');
var k = cust.closest('.kartica');
if (k) k.classList.add('cust-aktivan');
}
return;
}
var opt = e.target.closest('.cso');
if (opt) {
var cust = opt.closest('.cust-sel');
var selId = cust.getAttribute('data-sel');
var inp = selId ? document.getElementById(selId) : null;
var val = opt.getAttribute('data-val') || '';
var btnEl = cust.querySelector('.cust-sel-btn');
var btnTekst = btnEl ? btnEl.querySelector('.cust-sel-tekst') : null;
var naziv = opt.querySelector('.cso-naziv');
var prazan = opt.querySelector('.cso-prazno');
if (btnTekst) {
btnTekst.textContent = naziv ? naziv.textContent : (prazan ? prazan.textContent : '');
btnTekst.className = 'cust-sel-tekst' + (val ? '' : ' cso-prazno');
}
cust.querySelectorAll('.cso').forEach(function (o) { o.classList.remove('izabran'); });
if (val) opt.classList.add('izabran');
if (inp) {
inp.value = val;
inp.setAttribute('data-cena', opt.getAttribute('data-cena') || '0');
inp.setAttribute('data-stanje', opt.getAttribute('data-stanje') || '0');
inp.dispatchEvent(new Event('change', { bubbles: true }));
}
cust.classList.remove('otvoren');
var k2 = cust.closest('.kartica');
if (k2) k2.classList.remove('cust-aktivan');
return;
}
_custZatvoriSve();
});
// Radovi: pri izboru usluge (custom dropdown → hidden input) popuni predloženu cenu.
document.addEventListener('change', function (e) {
if (!e.target || e.target.id !== 'rad-usluga') return;
var cena = document.getElementById('rad-cena');
if (!cena) return;
var opt = e.target.options[e.target.selectedIndex];
var c = parseFloat(opt.getAttribute('data-cena'));
var c = parseFloat(e.target.getAttribute('data-cena'));
cena.value = (!isNaN(c) && c > 0) ? c.toFixed(2) : '0';
});
@@ -862,12 +954,16 @@ document.addEventListener('change', function (e) {
function popuniArtikle() {
ARTIKLI = window._ntechArtikli || [];
var izabrana = kategSel ? kategSel.value : '';
artikalSel.innerHTML = '<option value="">— odaberi artikal —</option>' +
ARTIKLI.filter(function (a) { return !izabrana || (a.KategorijaNaziv || '') === izabrana; })
.map(function (a) {
return '<option value="' + a.ID + '" data-cena="' + a.ProdajnaCena +
'" data-stanje="' + a.Kolicina + '">' + a.Naziv + ' — ' + parseFloat(a.ProdajnaCena||0).toFixed(2) + ' din</option>';
}).join('');
var cust = document.getElementById('deo-artikal-cust');
var lista = cust ? cust.querySelector('.cust-sel-lista') : null;
var html = '<div class="cso" data-val="" data-cena="0" data-stanje="0"><span class="cso-prazno">— odaberi artikal —</span></div>';
ARTIKLI.filter(function (a) { return !izabrana || (a.KategorijaNaziv || '') === izabrana; })
.forEach(function (a) {
html += '<div class="cso" data-val="' + a.ID + '" data-cena="' + a.ProdajnaCena +
'" data-stanje="' + (a.Kolicina || 0) + '"><span class="cso-naziv">' + a.Naziv +
'</span><span class="cso-cena">' + window._dinFmt(a.ProdajnaCena) + ' din</span></div>';
});
if (lista) lista.innerHTML = html;
if (umagacinu) umagacinu.textContent = '—';
}
window._ntechPopuniArtikle = popuniArtikle;
@@ -875,10 +971,9 @@ document.addEventListener('change', function (e) {
if (kategSel) kategSel.addEventListener('change', popuniArtikle);
artikalSel.addEventListener('change', function () {
var opt = artikalSel.options[artikalSel.selectedIndex];
var c = parseFloat(opt.getAttribute('data-cena'));
var c = parseFloat(artikalSel.getAttribute('data-cena'));
if (cena) cena.value = (!isNaN(c) && c > 0) ? c.toFixed(2) : '0';
var st = parseInt(opt.getAttribute('data-stanje'), 10);
var st = parseInt(artikalSel.getAttribute('data-stanje'), 10);
stanjeIzabrano = isNaN(st) ? 0 : st;
if (umagacinu) umagacinu.textContent = artikalSel.value ? stanjeIzabrano : '—';
osveziPrekoracenje();