Paginacija, interaktivna pretraga i optimizacija prikaza

- Dodata server-side paginacija za magacin (127 artikala) i klijente (1040)
  — Limit/Offset u ArtikalFilter i KlijentFilter, 100 po stranici
  — PrebrojiPoFilteru za izračunavanje ukupnog broja stranica
- Interaktivna pretraga (search-as-you-type) sa HTMX:
  — hx-trigger="keyup changed delay:300ms" na polju pretrage
  — HTMX menja samo #magacin-rezultati / #klijenti-rezultati
  — Polje pretrage ostaje u fokusu tokom osvežavanja
- Popravljena pretraga klijenata po imenu i prezimenu:
  — Dodato (ime || ' ' || prezime) LIKE u sva tri upita
  — "Ivana Lazić" sada pronalazi klijenta
- CSS optimizacije za velike liste:
  — content-visibility: auto na redovima tabela i karticama
  — contain-intrinsic-size za stabilan scroll
  — animation-delay produžen do 20. reda / 10. kartice
This commit is contained in:
2026-06-20 16:19:42 +02:00
parent 064d6dfa2a
commit a8f368ca06
8 changed files with 346 additions and 37 deletions
+12
View File
@@ -10,6 +10,7 @@ import (
// ArtikalRepository definiše operacije nad artiklima
type ArtikalRepository interface {
Lista(ctx context.Context, filter ArtikalFilter) ([]model.ArtikalSaKategorijom, error)
PrebrojiPoFilteru(ctx context.Context, filter ArtikalFilter) (int, error)
DohvatiID(ctx context.Context, id int64) (*model.Artikal, error)
Kreiraj(ctx context.Context, a *model.Artikal) (int64, error)
Izmeni(ctx context.Context, a *model.Artikal) error
@@ -78,6 +79,8 @@ type ArtikalFilter struct {
Pretraga string
KategorijaID *int64
SamoKriticni bool
Limit int
Offset int
}
// NabavkaRepository definiše operacije nad nabavkama
@@ -99,9 +102,18 @@ type DobavljacRepository interface {
Obrisi(ctx context.Context, id int64) error
}
// KlijentFilter definiše parametre za filtriranje liste klijenata
type KlijentFilter struct {
Pretraga string
Limit int
Offset int
}
// KlijentRepository definiše operacije nad klijentima
type KlijentRepository interface {
Lista(ctx context.Context, pretraga string) ([]model.Klijent, error)
ListaFilter(ctx context.Context, filter KlijentFilter) ([]model.Klijent, error)
PrebrojiPoFilteru(ctx context.Context, filter KlijentFilter) (int, error)
DohvatiID(ctx context.Context, id int64) (*model.Klijent, error)
Kreiraj(ctx context.Context, k *model.Klijent) (int64, error)
Izmeni(ctx context.Context, k *model.Klijent) error
+42
View File
@@ -50,6 +50,15 @@ func (r *ArtikalRepo) Lista(ctx context.Context, filter db.ArtikalFilter) ([]mod
upit += " ORDER BY a.naziv ASC"
if filter.Limit > 0 {
upit += " LIMIT ?"
args = append(args, filter.Limit)
if filter.Offset > 0 {
upit += " OFFSET ?"
args = append(args, filter.Offset)
}
}
redovi, err := r.db.QueryContext(ctx, upit, args...)
if err != nil {
return nil, fmt.Errorf("ntech: ArtikalRepo.Lista: %w", err)
@@ -258,3 +267,36 @@ func (r *ArtikalRepo) KorigujKolicinu(ctx context.Context, artikalID int64, nova
return tx.Commit()
}
// PrebrojiPoFilteru vraća ukupan broj artikala koji zadovoljavaju filter (bez LIMIT/OFFSET)
func (r *ArtikalRepo) PrebrojiPoFilteru(ctx context.Context, filter db.ArtikalFilter) (int, error) {
upit := `
SELECT COUNT(*)
FROM artikli a
LEFT JOIN kategorije k ON a.kategorija_id = k.id
WHERE 1=1`
args := []any{}
if filter.Pretraga != "" {
upit += " AND (a.naziv LIKE ? OR a.sifra LIKE ? OR a.barkod LIKE ? OR a.lokacija LIKE ? OR k.naziv LIKE ?)"
t := "%" + filter.Pretraga + "%"
args = append(args, t, t, t, t, t)
}
if filter.KategorijaID != nil {
upit += " AND a.kategorija_id = ?"
args = append(args, *filter.KategorijaID)
}
if filter.SamoKriticni {
upit += " AND a.kolicina <= a.kolicina_min"
}
var broj int
if err := r.db.QueryRowContext(ctx, upit, args...).Scan(&broj); err != nil {
return 0, fmt.Errorf("ntech: ArtikalRepo.PrebrojiPoFilteru: %w", err)
}
return broj, nil
}
+79 -3
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"fmt"
"ntech/internal/db"
"ntech/internal/model"
)
@@ -28,9 +29,9 @@ func (r *KlijentRepo) Lista(ctx context.Context, pretraga string) ([]model.Klije
args := []any{}
if pretraga != "" {
upit += " AND (ime LIKE ? OR prezime LIKE ? OR naziv_firme LIKE ?)"
p := "%" + pretraga + "%"
args = append(args, p, p, p)
upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR naziv_firme LIKE ? OR telefon LIKE ? OR email LIKE ?)"
p := "%" + pretraga + "%"
args = append(args, p, p, p, p, p, p)
}
upit += " ORDER BY datum_unosa DESC"
@@ -138,6 +139,81 @@ func (r *KlijentRepo) Izmeni(ctx context.Context, k *model.Klijent) error {
return nil
}
// ListaFilter vraća listu klijenata sa limitom i offsetom (paginacija)
func (r *KlijentRepo) ListaFilter(ctx context.Context, filter db.KlijentFilter) ([]model.Klijent, error) {
upit := `
SELECT id, tip, ime, prezime, jmbg, naziv_firme, pib, telefon, email, mesto, napomena, datum_unosa
FROM klijenti
WHERE 1=1`
args := []any{}
if filter.Pretraga != "" {
upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR naziv_firme LIKE ? OR telefon LIKE ? OR email LIKE ?)"
p := "%" + filter.Pretraga + "%"
args = append(args, p, p, p, p, p, p)
}
upit += " ORDER BY datum_unosa DESC"
if filter.Limit > 0 {
upit += " LIMIT ?"
args = append(args, filter.Limit)
if filter.Offset > 0 {
upit += " OFFSET ?"
args = append(args, filter.Offset)
}
}
redovi, err := r.db.QueryContext(ctx, upit, args...)
if err != nil {
return nil, fmt.Errorf("ntech: KlijentRepo.ListaFilter: %w", err)
}
defer redovi.Close()
var rezultat []model.Klijent
for redovi.Next() {
var k model.Klijent
var ime, prezime, jmbg, nazivFirme, pib, telefon, email, mesto, napomena sql.NullString
err := redovi.Scan(
&k.ID, &k.Tip, &ime, &prezime, &jmbg, &nazivFirme, &pib, &telefon, &email, &mesto, &napomena, &k.DatumUnosa,
)
if err != nil {
return nil, fmt.Errorf("ntech: KlijentRepo.ListaFilter: scan: %w", err)
}
k.Ime = ime.String
k.Prezime = prezime.String
k.JMBG = jmbg.String
k.NazivFirme = nazivFirme.String
k.PIB = pib.String
k.Telefon = telefon.String
k.Email = email.String
k.Mesto = mesto.String
k.Napomena = napomena.String
rezultat = append(rezultat, k)
}
return rezultat, nil
}
// PrebrojiPoFilteru vraća broj klijenata koji zadovoljavaju filter
func (r *KlijentRepo) PrebrojiPoFilteru(ctx context.Context, filter db.KlijentFilter) (int, error) {
upit := `SELECT COUNT(*) FROM klijenti WHERE 1=1`
args := []any{}
if filter.Pretraga != "" {
upit += " AND (ime LIKE ? OR prezime LIKE ? OR (ime || ' ' || prezime) LIKE ? OR naziv_firme LIKE ? OR telefon LIKE ? OR email LIKE ?)"
p := "%" + filter.Pretraga + "%"
args = append(args, p, p, p, p, p, p)
}
var broj int
if err := r.db.QueryRowContext(ctx, upit, args...).Scan(&broj); err != nil {
return 0, fmt.Errorf("ntech: KlijentRepo.PrebrojiPoFilteru: %w", err)
}
return broj, nil
}
// Obrisi briše klijenta po ID-u
func (r *KlijentRepo) Obrisi(ctx context.Context, id int64) error {
_, err := r.db.ExecContext(ctx, "DELETE FROM klijenti WHERE id = ?", id)
+61 -11
View File
@@ -3,8 +3,10 @@ package handler
import (
"log/slog"
"net/http"
"strconv"
"strings"
"ntech/internal/db"
"ntech/internal/db/sqlite"
"ntech/internal/model"
@@ -14,10 +16,16 @@ import (
// PodaciKlijenata su podaci za stranicu sa listom klijenata
type PodaciKlijenata struct {
model.PodaciStranice
Klijenti []model.Klijent
Pretraga string
Sacuvano bool
Obrisan bool
Klijenti []model.Klijent
Pretraga string
Sacuvano bool
Obrisan bool
StranicaBr int
UkupnoStranica int
UkupnoKlijenata int
StranicaPrev int
StranicaNext int
StranicaQueryUrl string
}
// PodaciFormeKlijenta su podaci za formu novog/izmenjenog klijenta
@@ -28,7 +36,7 @@ type PodaciFormeKlijenta struct {
Izmena bool
}
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
// Klijenti renderuje listu svih klijenata sa opcionom pretragom i paginacijom
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
if err != nil {
@@ -38,21 +46,63 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
pretraga := r.URL.Query().Get("pretraga")
klijenti, err := h.KlijentiRepo.Lista(r.Context(), pretraga)
const pageSize = 100
stranicaBr := 1
if p := r.URL.Query().Get("stranica"); p != "" {
if v, err := strconv.Atoi(p); err == nil && v > 0 {
stranicaBr = v
}
}
filter := db.KlijentFilter{
Pretraga: pretraga,
Limit: pageSize,
Offset: (stranicaBr - 1) * pageSize,
}
klijenti, err := h.KlijentiRepo.ListaFilter(r.Context(), filter)
if err != nil {
http.Error(w, "Greška pri učitavanju klijenata", http.StatusInternalServerError)
return
}
ukupno, err := h.KlijentiRepo.PrebrojiPoFilteru(r.Context(), filter)
if err != nil {
http.Error(w, "Greška pri učitavanju klijenata", http.StatusInternalServerError)
return
}
ukupnoStranica := (ukupno + pageSize - 1) / pageSize
queryDelići := ""
if pretraga != "" {
queryDelići += "&pretraga=" + pretraga
}
stranicaPrev := stranicaBr - 1
if stranicaPrev < 1 {
stranicaPrev = 1
}
stranicaNext := stranicaBr + 1
if stranicaNext > ukupnoStranica {
stranicaNext = ukupnoStranica
}
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "klijenti"
ps.NaslovStranice = "Klijenti"
podaci := PodaciKlijenata{
PodaciStranice: ps,
Klijenti: klijenti,
Pretraga: pretraga,
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
Obrisan: r.URL.Query().Get("obrisan") == "1",
PodaciStranice: ps,
Klijenti: klijenti,
Pretraga: pretraga,
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
Obrisan: r.URL.Query().Get("obrisan") == "1",
StranicaBr: stranicaBr,
UkupnoStranica: ukupnoStranica,
UkupnoKlijenata: ukupno,
StranicaPrev: stranicaPrev,
StranicaNext: stranicaNext,
StranicaQueryUrl: queryDelići,
}
h.renderujTemplate(w, "klijenti", podaci)
+67 -15
View File
@@ -14,13 +14,19 @@ import (
// PodaciMagacina su podaci za stranicu magacina
type PodaciMagacina struct {
model.PodaciStranice
Artikli []model.ArtikalSaKategorijom
Kategorije []model.Kategorija
Filter db.ArtikalFilter
KategorijaIDStr string
Sacuvano bool
Obrisan bool
Premesten bool
Artikli []model.ArtikalSaKategorijom
Kategorije []model.Kategorija
Filter db.ArtikalFilter
KategorijaIDStr string
Sacuvano bool
Obrisan bool
Premesten bool
StranicaBr int
UkupnoStranica int
UkupnoArtikala int
StranicaPrev int
StranicaNext int
StranicaQueryUrl string // čuva filtere za linkove paginacije
}
// Magacin renderuje listu artikala
@@ -45,12 +51,30 @@ func (h *Handler) Magacin(w http.ResponseWriter, r *http.Request) {
}
}
const pageSize = 100
stranicaBr := 1
if p := r.URL.Query().Get("stranica"); p != "" {
if v, err := strconv.Atoi(p); err == nil && v > 0 {
stranicaBr = v
}
}
filter.Limit = pageSize
filter.Offset = (stranicaBr - 1) * pageSize
artikli, err := h.Artikli.Lista(r.Context(), filter)
if err != nil {
http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError)
return
}
ukupno, err := h.Artikli.PrebrojiPoFilteru(r.Context(), filter)
if err != nil {
http.Error(w, "Greška pri učitavanju artikala", http.StatusInternalServerError)
return
}
ukupnoStranica := (ukupno + pageSize - 1) / pageSize
kategorije, err := h.KategorijeRepo.Lista(r.Context())
if err != nil {
http.Error(w, "Greška pri učitavanju kategorija", http.StatusInternalServerError)
@@ -60,15 +84,43 @@ func (h *Handler) Magacin(w http.ResponseWriter, r *http.Request) {
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "magacin"
ps.NaslovStranice = "Magacin"
// izgradi query string za paginaciju (čuva filtere)
queryDelići := ""
if v := filter.Pretraga; v != "" {
queryDelići += "&pretraga=" + v
}
if katIDStr != "" {
queryDelići += "&kategorija=" + katIDStr
}
if filter.SamoKriticni {
queryDelići += "&kriticni=1"
}
stranicaPrev := stranicaBr - 1
if stranicaPrev < 1 {
stranicaPrev = 1
}
stranicaNext := stranicaBr + 1
if stranicaNext > ukupnoStranica {
stranicaNext = ukupnoStranica
}
podaci := PodaciMagacina{
PodaciStranice: ps,
Artikli: artikli,
Kategorije: kategorije,
Filter: filter,
KategorijaIDStr: katIDStr,
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
Obrisan: r.URL.Query().Get("obrisan") == "1",
Premesten: r.URL.Query().Get("premesten") == "1",
PodaciStranice: ps,
Artikli: artikli,
Kategorije: kategorije,
Filter: filter,
KategorijaIDStr: katIDStr,
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
Obrisan: r.URL.Query().Get("obrisan") == "1",
Premesten: r.URL.Query().Get("premesten") == "1",
StranicaBr: stranicaBr,
UkupnoStranica: ukupnoStranica,
UkupnoArtikala: ukupno,
StranicaPrev: stranicaPrev,
StranicaNext: stranicaNext,
StranicaQueryUrl: queryDelići,
}
h.renderujTemplate(w, "magacin", podaci)
+28
View File
@@ -1109,6 +1109,29 @@ select {
.tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.tabela tbody tr:nth-child(11) { animation-delay: 0.44s; }
.tabela tbody tr:nth-child(12) { animation-delay: 0.48s; }
.tabela tbody tr:nth-child(13) { animation-delay: 0.52s; }
.tabela tbody tr:nth-child(14) { animation-delay: 0.56s; }
.tabela tbody tr:nth-child(15) { animation-delay: 0.60s; }
.tabela tbody tr:nth-child(16) { animation-delay: 0.64s; }
.tabela tbody tr:nth-child(17) { animation-delay: 0.68s; }
.tabela tbody tr:nth-child(18) { animation-delay: 0.72s; }
.tabela tbody tr:nth-child(19) { animation-delay: 0.76s; }
.tabela tbody tr:nth-child(20) { animation-delay: 0.80s; }
/* content-visibility: auto browser preskače render elemenata van viewport-a.
Ovo rešava problem sa 1000+ redova gde skrolovanje postaje prazno
dok browser ne stigne da iscrta sve. */
.tabela tbody tr {
content-visibility: auto;
contain-intrinsic-size: 48px;
}
[class*="-kartice"] > .animiraj,
.klijenti-kartice > .animiraj {
content-visibility: auto;
contain-intrinsic-size: 120px;
}
/* Stagger mobilnih lista-kartica (parnjak gornjeg za tabele). Pogađa kartice
unutar bilo kog .X-kartice kontejnera; ostali tipovi kartica (detalji/forma/
@@ -1118,6 +1141,11 @@ select {
[class*="-kartice"] > .animiraj:nth-child(3) { animation-delay: 0.16s; }
[class*="-kartice"] > .animiraj:nth-child(4) { animation-delay: 0.22s; }
[class*="-kartice"] > .animiraj:nth-child(5) { animation-delay: 0.28s; }
[class*="-kartice"] > .animiraj:nth-child(6) { animation-delay: 0.34s; }
[class*="-kartice"] > .animiraj:nth-child(7) { animation-delay: 0.40s; }
[class*="-kartice"] > .animiraj:nth-child(8) { animation-delay: 0.46s; }
[class*="-kartice"] > .animiraj:nth-child(9) { animation-delay: 0.52s; }
[class*="-kartice"] > .animiraj:nth-child(10) { animation-delay: 0.58s; }
/* Stagger naslaganih .kartica.animiraj na stranicama podešavanja/profila JEDNO mesto.
Descendant selektor (razmak, ne >): nth-child se računa po neposrednom roditelju kao i
+25 -3
View File
@@ -12,19 +12,24 @@
<div class="poruka-uspeh poruka-animacija">Klijent je uspešno obrisan.</div>
{{end}}
<!-- gornja traka: dugme + pretraga -->
<!-- gornja traka: dugme + interaktivna pretraga -->
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<a href="/klijenti/novi" class="btn-primarno">+ Novi klijent</a>
<form method="GET" action="/klijenti" style="display:flex;gap:8px;flex:1;min-width:200px;">
<form method="GET" action="/klijenti" style="display:flex;gap:8px;flex:1;min-width:200px;"
hx-get="/klijenti" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">
<input type="text" name="pretraga" value="{{.Pretraga}}"
placeholder="Pretraži po imenu ili nazivu firme..."
style="flex:1;">
style="flex:1;"
hx-trigger="keyup changed delay:300ms, search"
hx-get="/klijenti" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">
<button type="submit" class="btn-primarno">
Traži
</button>
</form>
</div>
<div id="klijenti-rezultati">
<!-- desktop tabela -->
<div class="kartica klijenti-tabela kartica-tabela animiraj">
<div class="tabela-skrol">
@@ -140,5 +145,22 @@
{{end}}
</div>
<!-- paginacija -->
{{if gt .UkupnoStranica 1}}
<div class="paginacija" style="display:flex;align-items:center;justify-content:center;gap:12px;padding:12px 0;flex-wrap:wrap;">
<span style="font-size:13px;color:var(--tekst-sporedni);">{{.UkupnoKlijenata}} klijenata — {{.StranicaBr}}/{{.UkupnoStranica}}</span>
<div style="display:flex;gap:4px;">
{{if gt .StranicaBr 1}}
<a href="?stranica={{.StranicaPrev}}{{.StranicaQueryUrl}}" class="btn-sekundarno-malo" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">← Prethodna</a>
{{end}}
{{if lt .StranicaBr .UkupnoStranica}}
<a href="?stranica={{.StranicaNext}}{{.StranicaQueryUrl}}" class="btn-primarno-malo" hx-target="#klijenti-rezultati" hx-select="#klijenti-rezultati" hx-swap="innerHTML" hx-push-url="true">Sledeća →</a>
{{end}}
</div>
</div>
{{end}}
</div><!-- kraj #klijenti-rezultati -->
</div>
{{end}}
+32 -5
View File
@@ -25,20 +25,27 @@
{{end}}
</div>
<!-- pretraga i filteri -->
<form method="GET" action="/magacin" class="kolona" style="gap:10px;">
<!-- pretraga i filteri — interaktivna pretraga (hx-trigger) -->
<form method="GET" action="/magacin" class="kolona" style="gap:10px;"
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
<input type="text" name="pretraga" value="{{.Filter.Pretraga}}"
placeholder="Pretraži artikle..."
style="width:100%;">
style="width:100%;"
hx-trigger="keyup changed delay:300ms, search"
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
<select name="kategorija" style="flex:1;min-width:140px;">
<select name="kategorija" style="flex:1;min-width:140px;"
hx-trigger="change"
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
<option value="">Sve kategorije</option>
{{range .Kategorije}}
<option value="{{.ID}}" {{if eq (printf "%d" .ID) $.KategorijaIDStr}}selected{{end}}>{{.Naziv}}</option>
{{end}}
</select>
<label style="display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--tekst-sporedni);cursor:pointer;padding:8px 12px;border:0.5px solid var(--ivica);border-radius:8px;background:var(--kartica);white-space:nowrap;">
<input type="checkbox" name="kriticni" value="1" {{if .Filter.SamoKriticni}}checked{{end}}>
<input type="checkbox" name="kriticni" value="1" {{if .Filter.SamoKriticni}}checked{{end}}
hx-trigger="change"
hx-get="/magacin" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">
Samo kritični
</label>
<button type="submit" class="btn-primarno">
@@ -47,6 +54,9 @@
</div>
</form>
<!-- rezultati pretrage — HTMX zamenjuje samo ovo, polje za pretragu ostaje u fokusu -->
<div id="magacin-rezultati">
<!-- tabela artikala -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="overflow-x:auto;">
@@ -162,6 +172,23 @@
{{end}}
</div>
<!-- paginacija -->
{{if gt .UkupnoStranica 1}}
<div class="paginacija" style="display:flex;align-items:center;justify-content:center;gap:12px;padding:12px 0;flex-wrap:wrap;">
<span style="font-size:13px;color:var(--tekst-sporedni);">{{.UkupnoArtikala}} artikala — {{.StranicaBr}}/{{.UkupnoStranica}}</span>
<div style="display:flex;gap:4px;">
{{if gt .StranicaBr 1}}
<a href="?stranica={{.StranicaPrev}}{{.StranicaQueryUrl}}" class="btn-sekundarno-malo" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">← Prethodna</a>
{{end}}
{{if lt .StranicaBr .UkupnoStranica}}
<a href="?stranica={{.StranicaNext}}{{.StranicaQueryUrl}}" class="btn-primarno-malo" hx-target="#magacin-rezultati" hx-select="#magacin-rezultati" hx-swap="innerHTML" hx-push-url="true">Sledeća →</a>
{{end}}
</div>
</div>
{{end}}
</div><!-- kraj #magacin-rezultati -->
</div>
{{end}}