Servis detalji: glatko osvežavanje bez treperenja i skoka
Sve forme na detaljima (dodavanje/brisanje rada i dela, promena statusa) sada osvežavaju ceo #glavni-sadrzaj preko data-refresh umesto punog reloda: status badge se ažurira odmah, nema skoka na vrh ni treptaja pozadine. AJAX grana dodaje bez-anim reflow (CSS #glavni-sadrzaj.bez-anim) i re-izvršava inline skripte da se combobox pretrage ponovo povežu nakon zamene.
This commit is contained in:
@@ -1100,6 +1100,14 @@ select {
|
|||||||
#dobavljaci-rezultati.bez-anim .tabela tbody tr,
|
#dobavljaci-rezultati.bez-anim .tabela tbody tr,
|
||||||
#dobavljaci-rezultati.bez-anim .animiraj { animation: none; }
|
#dobavljaci-rezultati.bez-anim .animiraj { animation: none; }
|
||||||
|
|
||||||
|
/* osvežavanje dela stranice preko data-refresh (npr. detalji naloga) ne sme da
|
||||||
|
re-animira kartice — sprečava treptaj pri dodavanju/brisanju i promeni statusa */
|
||||||
|
#glavni-sadrzaj.bez-anim .tabela tbody tr,
|
||||||
|
#glavni-sadrzaj.bez-anim .animiraj,
|
||||||
|
.bez-anim.kartica,
|
||||||
|
.bez-anim .tabela tbody tr,
|
||||||
|
.bez-anim .animiraj { animation: none; }
|
||||||
|
|
||||||
/* korisnikova preferencija animacije: body[data-animacija] nadjačava podrazumevano.
|
/* korisnikova preferencija animacije: body[data-animacija] nadjačava podrazumevano.
|
||||||
Kad korisnik odabere stil, animiraju se i redovi tabela i mobilne kartice. */
|
Kad korisnik odabere stil, animiraju se i redovi tabela i mobilne kartice. */
|
||||||
[data-animacija="bez"] .animiraj,
|
[data-animacija="bez"] .animiraj,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||||
{{template "status-badge-detalji" .Nalog.Status}}
|
{{template "status-badge-detalji" .Nalog.Status}}
|
||||||
<form method="post" action="/servis/{{.Nalog.ID}}/status" style="display:flex;align-items:center;gap:6px;" data-full-reload>
|
<form method="post" action="/servis/{{.Nalog.ID}}/status" style="display:flex;align-items:center;gap:6px;" data-refresh="#glavni-sadrzaj">
|
||||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
<select name="status" style="font-size:13px;padding:4px 8px;border-radius:6px;border:0.5px solid var(--ivica);background:var(--kartica-pozadina);color:var(--tekst-glavni);cursor:pointer;">
|
<select name="status" style="font-size:13px;padding:4px 8px;border-radius:6px;border:0.5px solid var(--ivica);background:var(--kartica-pozadina);color:var(--tekst-glavni);cursor:pointer;">
|
||||||
{{range .SviStatusi}}
|
{{range .SviStatusi}}
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
|
|
||||||
<!-- forma za dodavanje rada iz cenovnika usluga -->
|
<!-- forma za dodavanje rada iz cenovnika usluga -->
|
||||||
{{if index .Dozvole "servis.izmeni"}}
|
{{if index .Dozvole "servis.izmeni"}}
|
||||||
<form method="POST" action="/servis/{{.Nalog.ID}}/radovi" style="margin-bottom:16px;" data-refresh="#radovi-spisak">
|
<form method="POST" action="/servis/{{.Nalog.ID}}/radovi" style="margin-bottom:16px;" data-refresh="#glavni-sadrzaj">
|
||||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
<div style="display:grid;grid-template-columns:1fr 80px 120px auto;gap:10px;align-items:flex-end;">
|
<div style="display:grid;grid-template-columns:1fr 80px 120px auto;gap:10px;align-items:flex-end;">
|
||||||
<div>
|
<div>
|
||||||
@@ -217,7 +217,7 @@
|
|||||||
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
||||||
<td style="padding:9px 10px;text-align:center;">
|
<td style="padding:9px 10px;text-align:center;">
|
||||||
{{if index $.Dozvole "servis.izmeni"}}
|
{{if index $.Dozvole "servis.izmeni"}}
|
||||||
<form method="POST" action="/servis/{{$.Nalog.ID}}/radovi/{{.ID}}/obrisi" style="display:inline;" data-refresh="#radovi-spisak">
|
<form method="POST" action="/servis/{{$.Nalog.ID}}/radovi/{{.ID}}/obrisi" style="display:inline;" data-refresh="#glavni-sadrzaj">
|
||||||
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
data-potvrda="Ukloniti rad '{{.Naziv}}'?"
|
data-potvrda="Ukloniti rad '{{.Naziv}}'?"
|
||||||
@@ -247,21 +247,27 @@
|
|||||||
|
|
||||||
<!-- forma za dodavanje dela -->
|
<!-- forma za dodavanje dela -->
|
||||||
{{if index .Dozvole "servis.izmeni"}}
|
{{if index .Dozvole "servis.izmeni"}}
|
||||||
<form method="POST" action="/servis/{{.Nalog.ID}}/delovi" style="margin-bottom:16px;" data-refresh="#delovi-spisak">
|
<form method="POST" action="/servis/{{.Nalog.ID}}/delovi" style="margin-bottom:16px;" data-refresh="#glavni-sadrzaj">
|
||||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||||
<div style="display:grid;grid-template-columns:1fr 80px 120px auto;gap:10px;align-items:flex-end;">
|
<div style="margin-bottom:10px;">
|
||||||
|
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Kategorija</label>
|
||||||
|
<select id="deo-kategorija" style="width:100%;"></select>
|
||||||
|
</div>
|
||||||
|
<div style="display:grid;grid-template-columns:1fr 70px 80px 120px auto;gap:10px;align-items:flex-end;">
|
||||||
<div>
|
<div>
|
||||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
|
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
|
||||||
<input type="text" id="deo-artikal-pretraga" list="artikli-datalist" autocomplete="off"
|
<input type="text" id="deo-artikal-pretraga" list="artikli-datalist" autocomplete="off"
|
||||||
placeholder="Pretraži artikal..." style="width:100%;">
|
placeholder="Pretraži artikal..." style="width:100%;">
|
||||||
<datalist id="artikli-datalist">
|
<datalist id="artikli-datalist"></datalist>
|
||||||
{{range .Artikli}}<option value="{{.Naziv}} ({{.Kolicina}} kom)" data-id="{{.ID}}" data-cena="{{.ProdajnaCena}}">{{end}}
|
|
||||||
</datalist>
|
|
||||||
<input type="hidden" name="artikal_id" id="deo-artikal">
|
<input type="hidden" name="artikal_id" id="deo-artikal">
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">U magacinu</label>
|
||||||
|
<div id="deo-umagacinu" style="font-size:14px;font-weight:500;color:var(--tekst-glavni);text-align:center;padding:8px 0;">—</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Kol.</label>
|
<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>
|
<input type="number" name="kolicina" id="deo-kol" min="1" value="1" style="width:100%;" required>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
|
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
|
||||||
@@ -296,7 +302,7 @@
|
|||||||
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
<td style="padding:9px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{dinari .Ukupno}} din</td>
|
||||||
<td style="padding:9px 10px;text-align:center;">
|
<td style="padding:9px 10px;text-align:center;">
|
||||||
{{if index $.Dozvole "servis.izmeni"}}
|
{{if index $.Dozvole "servis.izmeni"}}
|
||||||
<form method="POST" action="/servis/{{$.Nalog.ID}}/delovi/{{.ID}}/obrisi" style="display:inline;" data-refresh="#delovi-spisak">
|
<form method="POST" action="/servis/{{$.Nalog.ID}}/delovi/{{.ID}}/obrisi" style="display:inline;" data-refresh="#glavni-sadrzaj">
|
||||||
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}">
|
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}">
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
data-potvrda="Ukloniti deo '{{.ArtikalNaziv}}'?"
|
data-potvrda="Ukloniti deo '{{.ArtikalNaziv}}'?"
|
||||||
@@ -368,6 +374,70 @@ function vežiCombobox(pretragaId, datalistId, hiddenId, cenaId) {
|
|||||||
pretraga.addEventListener('change', uskladi);
|
pretraga.addEventListener('change', uskladi);
|
||||||
}
|
}
|
||||||
vežiCombobox('rad-usluga-pretraga', 'usluge-datalist', 'rad-usluga', 'rad-cena');
|
vežiCombobox('rad-usluga-pretraga', 'usluge-datalist', 'rad-usluga', 'rad-cena');
|
||||||
vežiCombobox('deo-artikal-pretraga', 'artikli-datalist', 'deo-artikal', 'deo-cena');
|
|
||||||
|
// Delovi: kategorija filter + ograničenje količine na magacinsko stanje.
|
||||||
|
// Datalist se gradi iz JSON-a po izabranoj kategoriji; pri izboru artikla
|
||||||
|
// postavljamo cenu, max količine = stanje, i prikaz dostupne količine.
|
||||||
|
(function () {
|
||||||
|
var ARTIKLI = {{json .Artikli}} || [];
|
||||||
|
var kategSel = document.getElementById('deo-kategorija');
|
||||||
|
var pretraga = document.getElementById('deo-artikal-pretraga');
|
||||||
|
var datalist = document.getElementById('artikli-datalist');
|
||||||
|
var hidden = document.getElementById('deo-artikal');
|
||||||
|
var cena = document.getElementById('deo-cena');
|
||||||
|
var umagacinu = document.getElementById('deo-umagacinu');
|
||||||
|
if (!pretraga || !datalist) return;
|
||||||
|
|
||||||
|
function labela(a) { return a.Naziv + ' (' + a.Kolicina + ' kom)'; }
|
||||||
|
|
||||||
|
if (kategSel) {
|
||||||
|
var kategs = [];
|
||||||
|
ARTIKLI.forEach(function (a) {
|
||||||
|
var k = a.KategorijaNaziv || '';
|
||||||
|
if (k && kategs.indexOf(k) < 0) kategs.push(k);
|
||||||
|
});
|
||||||
|
kategs.sort();
|
||||||
|
kategSel.innerHTML = '<option value="">Sve kategorije</option>' +
|
||||||
|
kategs.map(function (k) { return '<option value="' + k + '">' + k + '</option>'; }).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function popuniDatalist() {
|
||||||
|
var izabrana = kategSel ? kategSel.value : '';
|
||||||
|
datalist.innerHTML = ARTIKLI
|
||||||
|
.filter(function (a) { return !izabrana || (a.KategorijaNaziv || '') === izabrana; })
|
||||||
|
.map(function (a) {
|
||||||
|
return '<option value="' + labela(a) + '" data-id="' + a.ID +
|
||||||
|
'" data-cena="' + a.ProdajnaCena + '" data-stanje="' + a.Kolicina + '">';
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
popuniDatalist();
|
||||||
|
|
||||||
|
if (kategSel) kategSel.addEventListener('change', function () {
|
||||||
|
popuniDatalist();
|
||||||
|
pretraga.value = '';
|
||||||
|
hidden.value = '';
|
||||||
|
if (umagacinu) umagacinu.textContent = '—';
|
||||||
|
});
|
||||||
|
|
||||||
|
function uskladi() {
|
||||||
|
var opt = null, opts = datalist.options;
|
||||||
|
for (var i = 0; i < opts.length; i++) {
|
||||||
|
if (opts[i].value === pretraga.value) { opt = opts[i]; break; }
|
||||||
|
}
|
||||||
|
if (opt) {
|
||||||
|
hidden.value = opt.dataset.id || '';
|
||||||
|
if (cena) {
|
||||||
|
var c = parseFloat(opt.dataset.cena);
|
||||||
|
cena.value = (!isNaN(c) && c > 0) ? c.toFixed(2) : '0';
|
||||||
|
}
|
||||||
|
if (umagacinu) umagacinu.textContent = (parseInt(opt.dataset.stanje, 10) || 0);
|
||||||
|
} else {
|
||||||
|
hidden.value = '';
|
||||||
|
if (umagacinu) umagacinu.textContent = '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pretraga.addEventListener('input', uskladi);
|
||||||
|
pretraga.addEventListener('change', uskladi);
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -393,8 +393,20 @@
|
|||||||
var doc = new DOMParser().parseFromString(html, 'text/html');
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
var novi = doc.querySelector(ciljSel);
|
var novi = doc.querySelector(ciljSel);
|
||||||
var stari = document.querySelector(ciljSel);
|
var stari = document.querySelector(ciljSel);
|
||||||
if (novi && stari) stari.replaceWith(novi);
|
if (novi && stari) {
|
||||||
f.reset();
|
// bez-anim spречava treptaj re-animacije kartica pri zameni
|
||||||
|
novi.classList.add('bez-anim');
|
||||||
|
stari.replaceWith(novi);
|
||||||
|
void novi.offsetHeight;
|
||||||
|
novi.classList.remove('bez-anim');
|
||||||
|
// replaceWith ne pokreće <script> iz fragmenta — re-izvrši ih
|
||||||
|
// ručno (npr. da se combobox/pretrage ponovo povežu)
|
||||||
|
novi.querySelectorAll('script').forEach(function (old) {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
if (old.src) s.src = old.src; else s.textContent = old.textContent;
|
||||||
|
old.parentNode.replaceChild(s, old);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (btn) btn.disabled = false;
|
if (btn) btn.disabled = false;
|
||||||
})
|
})
|
||||||
.catch(function () { location.href = res.url; });
|
.catch(function () { location.href = res.url; });
|
||||||
|
|||||||
Reference in New Issue
Block a user