Servis: auto-snimanje forme izmene + hx-boost navigacija (bez bljeska)
- Forma Izmeni nalog snima se automatski (change na celoj formi, X-Autosave→204), bez dugmeta Sačuvaj - Ispravka: trazene_nadogradnje je NOT NULL — slati prazan string umesto NULL - Iz kartice detalja uklonjeni dropdown statusa i datum završetka - Forma izmene više ne ubacuje težak JSON svih klijenata (nepotreban u izmeni) - Navigacioni linkovi (Kategorije, Izmeni, Nazad) dobili hx-boost — swap #glavni-sadrzaj bez punog reloda, pozadina ne trepće
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
{{end}}
|
||||
|
||||
<!-- nazad dugme -->
|
||||
<a href="/magacin" class="nazad-link">
|
||||
<a href="/magacin" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||
<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"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
Nazad na magacin
|
||||
</a>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<a href="/magacin/novi?tip={{.Tip}}" class="btn-primarno">+ {{if eq .Tip "usluga"}}Nova usluga{{else if eq .Tip "trosak"}}Novi trošak{{else}}Novi artikal{{end}}</a>
|
||||
{{end}}
|
||||
{{if and (eq .Tip "proizvod") (index .Dozvole "kategorija.pregled")}}
|
||||
<a href="/magacin/kategorije" class="btn-primarno">Kategorije</a>
|
||||
<a href="/magacin/kategorije" class="btn-primarno" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">Kategorije</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
{{end}}
|
||||
|
||||
<!-- nazad dugme -->
|
||||
<a href="/servis" class="nazad-link">
|
||||
<a href="/servis" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||
<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 servis
|
||||
</a>
|
||||
@@ -44,22 +44,6 @@
|
||||
</span>
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||
{{template "status-badge-detalji" .Nalog.Status}}
|
||||
<form method="post" action="/servis/{{.Nalog.ID}}/status" style="display:flex;align-items:center;gap:6px;" data-refresh="#nalog-zaglavlje">
|
||||
<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;"{{if .ZakljucanStatus}} disabled title="Status zaključan dok ima delova koji nedostaju"{{end}}>
|
||||
{{range .SviStatusi}}
|
||||
{{/* „Čeka delove" je automatski status — ne nudi se ručno, osim ako je nalog već u njemu */}}
|
||||
{{if or (ne . "Čeka delove") (eq . $.Nalog.Status)}}
|
||||
<option value="{{.}}"{{if eq . $.Nalog.Status}} selected{{end}}>{{.}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
{{if .ZakljucanStatus}}
|
||||
<span style="font-size:12px;color:#dc2626;">🔒 Status zaključan — obrišite delove koji nedostaju</span>
|
||||
{{else}}
|
||||
<button type="submit" class="btn-sekundarno" style="font-size:13px;padding:4px 12px;">Promeni</button>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||||
@@ -76,7 +60,7 @@
|
||||
Otpremnica
|
||||
</a>
|
||||
{{end}}
|
||||
<a href="/servis/izmeni/{{.Nalog.ID}}" class="btn-primarno">
|
||||
<a href="/servis/izmeni/{{.Nalog.ID}}" class="btn-primarno" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||
Izmeni nalog
|
||||
</a>
|
||||
</div>
|
||||
@@ -92,12 +76,6 @@
|
||||
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.PredvidjenDatum.Format "02.01.2006."}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Nalog.DatumZavrsetka}}
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Datum završetka</div>
|
||||
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.DatumZavrsetka.Format "02.01.2006."}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Garancija do</div>
|
||||
<div style="font-size:14px;{{if .Nalog.GarancijaDo}}color:#16a34a;font-weight:500;{{else}}color:var(--tekst-sporedni);{{end}}">
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
{{define "sadrzaj"}}
|
||||
<div style="width:100%;">
|
||||
|
||||
<!-- podaci za Alpine: lista klijenata i eventualno već vezani klijent (kod izmene) -->
|
||||
{{if not .Izmena}}
|
||||
<!-- podaci za Alpine pretragu klijenta — potrebni samo pri novom nalogu;
|
||||
u izmeni se blok klijenta ne prikazuje, pa ne ubacujemo težak inline JSON svih klijenata
|
||||
(blokirao bi render forme i pravio bljesak pozadine pri otvaranju) -->
|
||||
<script>
|
||||
window.__klijentiNaloga = {{json .Klijenti}};
|
||||
window.__izabraniKlijentId = {{.Nalog.KlijentIDVrednost}};
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
<!-- nazad dugme -->
|
||||
<a href="{{if .Izmena}}/servis/{{.Nalog.ID}}{{else}}/servis{{end}}" class="nazad-link">
|
||||
<a href="{{if .Izmena}}/servis/{{.Nalog.ID}}{{else}}/servis{{end}}" class="nazad-link" hx-boost="true" hx-target="#glavni-sadrzaj" hx-select="#glavni-sadrzaj" hx-swap="outerHTML">
|
||||
<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>
|
||||
{{if .Izmena}}Nazad na nalog{{else}}Nazad na servis{{end}}
|
||||
</a>
|
||||
@@ -21,7 +25,7 @@
|
||||
<div class="poruka-greska greska-animacija" style="margin-bottom:16px;">{{.Greska}}</div>
|
||||
{{end}}
|
||||
|
||||
<form method="POST" action="{{if .Izmena}}/servis/izmeni/{{.Nalog.ID}}{{else}}/servis/novi{{end}}">
|
||||
<form id="forma-nalog" method="POST" action="{{if .Izmena}}/servis/izmeni/{{.Nalog.ID}}{{else}}/servis/novi{{end}}"{{if .Izmena}} data-autosave="1"{{end}}>
|
||||
|
||||
<!-- broj naloga ide kao hidden — server ga je generisao, vraćamo ga pri POST-u -->
|
||||
<input type="hidden" name="broj_naloga" value="{{.Nalog.BrojNaloga}}">
|
||||
@@ -304,18 +308,75 @@
|
||||
style="width:100%;resize:vertical;">{{.Nalog.Napomena}}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- dugmad -->
|
||||
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:18px;">
|
||||
<a href="{{if .Izmena}}/servis/{{.Nalog.ID}}{{else}}/servis{{end}}" class="btn-sekundarno">Odustani</a>
|
||||
<button type="submit" class="btn-primarno">
|
||||
{{if .Izmena}}Sačuvaj izmene{{else}}Sačuvaj nalog{{end}}
|
||||
</button>
|
||||
{{if .Izmena}}
|
||||
<!-- izmena se snima automatski u realnom vremenu; indikator stanja umesto dugmeta -->
|
||||
<div style="display:flex;justify-content:flex-end;align-items:center;min-height:24px;margin-top:18px;">
|
||||
<span id="autosave-status" style="font-size:13px;color:var(--tekst-sporedni);transition:opacity 0.3s;opacity:0;"></span>
|
||||
</div>
|
||||
{{else}}
|
||||
<!-- dugmad (samo za novi nalog) -->
|
||||
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:18px;">
|
||||
<a href="/servis" class="btn-sekundarno">Odustani</a>
|
||||
<button type="submit" class="btn-primarno">Sačuvaj nalog</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// automatsko snimanje forme „Izmeni nalog" — okida se na `change` bilo kog polja.
|
||||
// `change` se za tekst/textarea/number okine na blur (samo ako je vrednost promenjena),
|
||||
// a za select/date/checkbox na izbor — jedan slušalac pokriva sve tipove.
|
||||
(function () {
|
||||
var forma = document.getElementById('forma-nalog');
|
||||
if (!forma || forma.dataset.autosave !== '1') return;
|
||||
var status = document.getElementById('autosave-status');
|
||||
var slanjeUToku = false;
|
||||
var ponoviPoZavrsetku = false;
|
||||
|
||||
function prikaziStatus(tekst, boja) {
|
||||
if (!status) return;
|
||||
status.textContent = tekst;
|
||||
status.style.color = boja;
|
||||
status.style.opacity = '1';
|
||||
}
|
||||
function sakrijStatus() {
|
||||
if (status) status.style.opacity = '0';
|
||||
}
|
||||
|
||||
function snimi() {
|
||||
// ako je slanje već u toku, zapamti da treba ponovo snimiti kad se završi
|
||||
if (slanjeUToku) { ponoviPoZavrsetku = true; return; }
|
||||
slanjeUToku = true;
|
||||
prikaziStatus('Čuvam…', 'var(--tekst-sporedni)');
|
||||
var podaci = new URLSearchParams(new FormData(forma));
|
||||
fetch(forma.action, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Autosave': '1' },
|
||||
body: podaci,
|
||||
}).then(function (odgovor) {
|
||||
if (odgovor.ok) {
|
||||
prikaziStatus('Sačuvano ✓', '#16a34a');
|
||||
setTimeout(sakrijStatus, 2000);
|
||||
} else {
|
||||
return odgovor.text().then(function (poruka) {
|
||||
prikaziStatus(poruka.trim() || 'Greška pri čuvanju', '#dc2626');
|
||||
});
|
||||
}
|
||||
}).catch(function () {
|
||||
prikaziStatus('Greška pri čuvanju', '#dc2626');
|
||||
}).finally(function () {
|
||||
slanjeUToku = false;
|
||||
if (ponoviPoZavrsetku) { ponoviPoZavrsetku = false; snimi(); }
|
||||
});
|
||||
}
|
||||
|
||||
forma.addEventListener('change', snimi);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tip-opcija {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user