From dfad0ff1f4d5d24749d04af05e28a9b97c1b520b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sun, 14 Jun 2026 08:16:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(nabavka):=20brzi=20unos=20dobavlja=C4=8Da?= =?UTF-8?q?=20i=20artikla=20iz=20forme=20nabavke?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dugmad otvaraju modale koji preko AJAX-a (X-Requested-With: fetch) čuvaju novi zapis i vraćaju JSON; novi dobavljač se odmah ubacuje u listu i bira. Modal artikla proširen na sva polja kao puna stranica. Centriranje modala prebačeno u .modal-overlay klasu (x-show je brisao inline display:flex). --- internal/handler/dobavljac.go | 19 +++- web/static/js/ntech.js | 81 +++++++++++++++ web/templates/stranice/nabavka_forma.html | 120 +++++++++++++++++++++- 3 files changed, 215 insertions(+), 5 deletions(-) diff --git a/internal/handler/dobavljac.go b/internal/handler/dobavljac.go index 1e92408..9d036d7 100644 --- a/internal/handler/dobavljac.go +++ b/internal/handler/dobavljac.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" "strings" @@ -85,7 +86,15 @@ func (h *Handler) SacuvajDobavljaca(w http.ResponseWriter, r *http.Request) { } dobavljac, greska := parseFormuDobavljaca(r) + jeAjax := r.Header.Get("X-Requested-With") == "fetch" if greska != "" { + // fetch zahtev (iz modala u nabavci) dobija JSON grešku + if jeAjax { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, `{"greska":%q}`, greska) + return + } podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) ps := h.popuniPodaciStranice(r, podesavanja) ps.Stranica = "dobavljaci" @@ -99,11 +108,19 @@ func (h *Handler) SacuvajDobavljaca(w http.ResponseWriter, r *http.Request) { return } - if _, err := h.DobavljaciRepo.Kreiraj(r.Context(), &dobavljac); err != nil { + id, err := h.DobavljaciRepo.Kreiraj(r.Context(), &dobavljac) + if err != nil { http.Error(w, "Greška pri čuvanju dobavljača", http.StatusInternalServerError) return } + // fetch zahtev (iz modala) dobija JSON sa ID-em i nazivom novog dobavljača + if jeAjax { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"id":%d,"naziv":%q}`, id, dobavljac.Naziv) + return + } + http.Redirect(w, r, "/dobavljaci?sacuvano=1", http.StatusSeeOther) } diff --git a/web/static/js/ntech.js b/web/static/js/ntech.js index 2e6b4c2..49e10eb 100644 --- a/web/static/js/ntech.js +++ b/web/static/js/ntech.js @@ -194,7 +194,22 @@ document.addEventListener('alpine:init', () => { modalGreska: '', modalNaziv: '', modalKategorijaID: '', + modalOpis: '', + modalKolicina: '', + modalKolicinaMin: '', modalCena: '', + modalLokacija: '', + modalNapomena: '', + modalDob: false, + modalDobUcitavanje: false, + modalDobGreska: '', + modalDobNaziv: '', + modalDobKontakt: '', + modalDobTelefon: '', + modalDobEmail: '', + modalDobPib: '', + modalDobMesto: '', + modalDobNapomena: '', init() { this.artikliOpcije = window._ntechArtikli || [] this.isMobile = window.matchMedia('(max-width: 768px)').matches @@ -219,7 +234,12 @@ document.addEventListener('alpine:init', () => { this.modalGreska = '' this.modalNaziv = '' this.modalKategorijaID = '' + this.modalOpis = '' + this.modalKolicina = '' + this.modalKolicinaMin = '' this.modalCena = '' + this.modalLokacija = '' + this.modalNapomena = '' this.$nextTick(() => this.$refs.modalNazivInput && this.$refs.modalNazivInput.focus()) }, zatvoriModal() { @@ -235,7 +255,12 @@ document.addEventListener('alpine:init', () => { const params = new URLSearchParams() params.append('naziv', this.modalNaziv.trim()) if (this.modalKategorijaID) params.append('kategorija_id', this.modalKategorijaID) + params.append('opis', this.modalOpis.trim()) + if (this.modalKolicina) params.append('kolicina', this.modalKolicina) + if (this.modalKolicinaMin) params.append('kolicina_min', this.modalKolicinaMin) if (this.modalCena) params.append('prodajna_cena', this.modalCena) + params.append('lokacija', this.modalLokacija.trim()) + params.append('napomena', this.modalNapomena.trim()) params.append('_csrf', document.querySelector('meta[name=csrf-token]')?.content || '') try { const odgovor = await fetch('/magacin/novi', { @@ -255,6 +280,62 @@ document.addEventListener('alpine:init', () => { } finally { this.modalUcitavanje = false } + }, + otvoriModalDobavljac() { + this.modalDob = true + this.modalDobGreska = '' + this.modalDobNaziv = '' + this.modalDobKontakt = '' + this.modalDobTelefon = '' + this.modalDobEmail = '' + this.modalDobPib = '' + this.modalDobMesto = '' + this.modalDobNapomena = '' + this.$nextTick(() => this.$refs.modalDobNazivInput && this.$refs.modalDobNazivInput.focus()) + }, + zatvoriModalDobavljac() { + this.modalDob = false + }, + async sacuvajDobavljaca() { + if (!this.modalDobNaziv.trim()) { + this.modalDobGreska = 'Naziv dobavljača je obavezan.' + return + } + this.modalDobUcitavanje = true + this.modalDobGreska = '' + const params = new URLSearchParams() + params.append('naziv', this.modalDobNaziv.trim()) + params.append('kontakt_osoba', this.modalDobKontakt.trim()) + params.append('telefon', this.modalDobTelefon.trim()) + params.append('email', this.modalDobEmail.trim()) + params.append('pib', this.modalDobPib.trim()) + params.append('mesto', this.modalDobMesto.trim()) + params.append('napomena', this.modalDobNapomena.trim()) + params.append('_csrf', document.querySelector('meta[name=csrf-token]')?.content || '') + try { + const odgovor = await fetch('/dobavljaci/novi', { + method: 'POST', + headers: {'X-Requested-With': 'fetch', 'Content-Type': 'application/x-www-form-urlencoded'}, + body: params + }) + if (!odgovor.ok) { + const greska = await odgovor.json().catch(() => null) + this.modalDobGreska = (greska && greska.greska) || 'Greška pri čuvanju dobavljača. Pokušajte ponovo.' + return + } + const novi = await odgovor.json() + // dodaj novog dobavljača u padajuću listu i odmah ga izaberi + const opcija = document.createElement('option') + opcija.value = novi.id + opcija.textContent = novi.naziv + this.$refs.selDobavljac.appendChild(opcija) + this.$refs.selDobavljac.value = novi.id + this.zatvoriModalDobavljac() + } catch { + this.modalDobGreska = 'Greška pri komunikaciji sa serverom.' + } finally { + this.modalDobUcitavanje = false + } } })) diff --git a/web/templates/stranice/nabavka_forma.html b/web/templates/stranice/nabavka_forma.html index 3f57bbc..b916d30 100644 --- a/web/templates/stranice/nabavka_forma.html +++ b/web/templates/stranice/nabavka_forma.html @@ -8,6 +8,8 @@ .forma-kartica:nth-child(1) { animation-delay: 0.04s; } .forma-kartica:nth-child(2) { animation-delay: 0.12s; } .modal-sadrzaj { animation: modalIn 0.25s ease forwards; } + /* centriranje preko klase (ne inline) — x-show menja inline display, pa bi obrisao flex i modal bi pao u ćošak */ + .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 50; display: flex; align-items: center; justify-content: center; padding: 16px; } {{end}} @@ -37,8 +39,15 @@
- - {{range .Dobavljaci}} @@ -187,10 +196,10 @@ x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" - style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:50;display:flex;align-items:center;justify-content:center;padding:16px;" + class="modal-overlay" @click.self="zatvoriModal()" @keydown.escape.window="zatvoriModal()"> - + + +
{{end}}