feat(pdv): šifarnik PDV stopa — handler, rute i UI (Faza 1)

Handleri (prikaz, dodaj, izmeni, arhiviraj/vrati) sa validacijom i flash
porukama; rute pod /admin/podesavanja/pdv-stope (dozvole podesavanja.*);
stranica pdv_stope registrovana u kes.go i dodata u meni Podešavanja.
Šifarnik je opšti (bez RequireModul) jer ga koristi i kalkulacija.
This commit is contained in:
2026-06-13 20:45:43 +02:00
parent 15995167b7
commit d06a353a52
5 changed files with 268 additions and 10 deletions
+15 -7
View File
@@ -8,9 +8,9 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog" "log/slog"
"mime"
"net/http" "net/http"
"os" "os"
"mime"
"path/filepath" "path/filepath"
"sort" "sort"
"strconv" "strconv"
@@ -87,12 +87,14 @@ func main() {
db, err := sqlite.OtvoriDB(putanjaBaze) db, err := sqlite.OtvoriDB(putanjaBaze)
if err != nil { if err != nil {
slog.Error("Greška pri otvaranju baze", "error", err); os.Exit(1) slog.Error("Greška pri otvaranju baze", "error", err)
os.Exit(1)
} }
defer db.Close() defer db.Close()
if err := sqlite.PokreniMigracije(db, migrFS); err != nil { if err := sqlite.PokreniMigracije(db, migrFS); err != nil {
slog.Error("Greška pri migracijama", "error", err); os.Exit(1) slog.Error("Greška pri migracijama", "error", err)
os.Exit(1)
} }
slog.Info("migracije uspešno izvršene") slog.Info("migracije uspešno izvršene")
@@ -111,7 +113,8 @@ func main() {
// ključ za šifrovanje TOTP tajni u mirovanju (AES-256-GCM) // ključ za šifrovanje TOTP tajni u mirovanju (AES-256-GCM)
totpKljuc, err := ucitajTotpKljuc() totpKljuc, err := ucitajTotpKljuc()
if err != nil { if err != nil {
slog.Error("Greška pri učitavanju ključa za TOTP", "error", err); os.Exit(1) slog.Error("Greška pri učitavanju ključa za TOTP", "error", err)
os.Exit(1)
} }
// jednokratno šifruj eventualne stare TOTP tajne koje su ostale kao čist tekst // jednokratno šifruj eventualne stare TOTP tajne koje su ostale kao čist tekst
@@ -137,7 +140,8 @@ func main() {
if os.Getenv("NTECH_ENV") == "production" { if os.Getenv("NTECH_ENV") == "production" {
kes, err := handler.KreirajKes(templFS) kes, err := handler.KreirajKes(templFS)
if err != nil { if err != nil {
slog.Error("Greška pri kreiranju keša šablona", "error", err); os.Exit(1) slog.Error("Greška pri kreiranju keša šablona", "error", err)
os.Exit(1)
} }
h.Templates = kes h.Templates = kes
slog.Info("keš šablona kreiran", "broj", len(kes)) slog.Info("keš šablona kreiran", "broj", len(kes))
@@ -229,6 +233,10 @@ func main() {
r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste) r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste)
r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled) r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled)
r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem) r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem)
r.Get("/admin/podesavanja/pdv-stope", h.PdvStope)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/aktivnost", h.PromeniAktivnostPdvStope)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/sacuvaj", h.SacuvajPodesavanja) r.With(doz("podesavanja.izmeni")).Post("/podesavanja/sacuvaj", h.SacuvajPodesavanja)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/logo", h.OtpremiLogo) r.With(doz("podesavanja.izmeni")).Post("/podesavanja/logo", h.OtpremiLogo)
r.With(doz("podesavanja.login_pozadina")).Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu) r.With(doz("podesavanja.login_pozadina")).Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu)
@@ -327,7 +335,8 @@ func main() {
slog.Info("NTech pokrenut", "port", port) slog.Info("NTech pokrenut", "port", port)
err = http.ListenAndServe(":"+port, r) err = http.ListenAndServe(":"+port, r)
if err != nil { if err != nil {
slog.Error("port je zauzet ili nije dostupan", "port", port); os.Exit(1) slog.Error("port je zauzet ili nije dostupan", "port", port)
os.Exit(1)
} }
} }
@@ -419,4 +428,3 @@ func ocistiStareBackupe(folder string, max int) {
_ = os.Remove(f) _ = os.Remove(f)
} }
} }
+1
View File
@@ -25,6 +25,7 @@ var saSidebar = []string{
"magacin", "magacin_forma", "magacin", "magacin_forma",
"nabavke", "nabavka_forma", "nabavka_detalji", "nabavke", "nabavka_forma", "nabavka_detalji",
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem",
"pdv_stope",
"podsetnici", "podsetnik_forma", "podsetnici", "podsetnik_forma",
"profil_tema", "profil_tema",
"prodaja", "prodaja_detalji", "prodaja_forma", "prodaja", "prodaja_detalji", "prodaja_forma",
+154
View File
@@ -0,0 +1,154 @@
package handler
import (
"net/http"
"strconv"
"strings"
"ntech/internal/db/sqlite"
"ntech/internal/middleware"
"ntech/internal/model"
"github.com/go-chi/chi/v5"
)
// validneOznakeStope su dozvoljene oznake PDV stope (vrsta po zakonu)
var validneOznakeStope = map[string]bool{
"opsta": true,
"posebna": true,
"oslobodjeno": true,
}
// PodaciPdvStope su podaci za stranicu šifarnika PDV stopa
type PodaciPdvStope struct {
model.PodaciStranice
Stope []model.PdvStopa
}
// PdvStope renderuje šifarnik PDV stopa (sve stope, uključujući arhivirane)
func (h *Handler) PdvStope(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
return
}
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
if err != nil {
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
return
}
stope, err := h.PdvStopeRepo.Lista(r.Context(), false)
if err != nil {
http.Error(w, "Greška pri učitavanju PDV stopa", http.StatusInternalServerError)
return
}
ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "podesavanja-pdv-stope"
ps.NaslovStranice = "PDV stope"
h.renderujTemplate(w, "pdv_stope", PodaciPdvStope{PodaciStranice: ps, Stope: stope})
}
// parsePdvStopuForma čita i proverava polja forme; vraća popunjenu stopu i poruku o grešci
func parsePdvStopuForma(r *http.Request) (model.PdvStopa, string) {
naziv := strings.TrimSpace(r.FormValue("naziv"))
oznaka := strings.TrimSpace(r.FormValue("oznaka"))
stopaTekst := strings.TrimSpace(strings.Replace(r.FormValue("stopa"), ",", ".", 1))
redosled, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("redosled")))
if naziv == "" {
return model.PdvStopa{}, "Naziv stope je obavezan."
}
if !validneOznakeStope[oznaka] {
return model.PdvStopa{}, "Oznaka mora biti opšta, posebna ili oslobođeno."
}
stopa, err := strconv.ParseFloat(stopaTekst, 64)
if err != nil || stopa < 0 || stopa > 100 {
return model.PdvStopa{}, "Stopa mora biti broj između 0 i 100."
}
return model.PdvStopa{
Naziv: naziv,
Stopa: stopa,
Oznaka: oznaka,
Aktivna: true,
Redosled: redosled,
}, ""
}
// DodajPdvStopu prima POST i upisuje novu stopu u šifarnik
func (h *Handler) DodajPdvStopu(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.izmeni"); !ok {
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
return
}
stopa, greska := parsePdvStopuForma(r)
if greska != "" {
middleware.SetFlash(w, r, h.DB, "greska", greska)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
return
}
if _, err := h.PdvStopeRepo.Kreiraj(r.Context(), &stopa); err != nil {
http.Error(w, "Greška pri čuvanju PDV stope", http.StatusInternalServerError)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je dodata.")
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
}
// IzmeniPdvStopu prima POST i menja postojeću stopu
func (h *Handler) IzmeniPdvStopu(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.izmeni"); !ok {
return
}
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "Neispravan ID stope", http.StatusBadRequest)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
return
}
stopa, greska := parsePdvStopuForma(r)
if greska != "" {
middleware.SetFlash(w, r, h.DB, "greska", greska)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
return
}
stopa.ID = id
if err := h.PdvStopeRepo.Izmeni(r.Context(), &stopa); err != nil {
http.Error(w, "Greška pri izmeni PDV stope", http.StatusInternalServerError)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je izmenjena.")
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
}
// PromeniAktivnostPdvStope arhivira ili vraća stopu u upotrebu (toggle, bez brisanja)
func (h *Handler) PromeniAktivnostPdvStope(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.izmeni"); !ok {
return
}
id, err := parseID(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "Neispravan ID stope", http.StatusBadRequest)
return
}
postojeca, err := h.PdvStopeRepo.DohvatiID(r.Context(), id)
if err != nil {
http.Error(w, "PDV stopa nije pronađena", http.StatusNotFound)
return
}
if err := h.PdvStopeRepo.PostaviAktivnu(r.Context(), id, !postojeca.Aktivna); err != nil {
http.Error(w, "Greška pri promeni statusa PDV stope", http.StatusInternalServerError)
return
}
poruka := "PDV stopa je arhivirana."
if !postojeca.Aktivna {
poruka = "PDV stopa je vraćena u upotrebu."
}
middleware.SetFlash(w, r, h.DB, "uspeh", poruka)
http.Redirect(w, r, "/admin/podesavanja/pdv-stope", http.StatusSeeOther)
}
+8 -3
View File
@@ -147,19 +147,19 @@
{{if index .Dozvole "podesavanja.pregled"}} {{if index .Dozvole "podesavanja.pregled"}}
<div> <div>
<button type="button" data-podmeni-dugme <button type="button" data-podmeni-dugme
class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}aktivan{{end}}" class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;"> style="width:100%;background:none;border:none;cursor:pointer;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>Podešavanja</span> <span>Podešavanja</span>
<span class="nav-strelica"> <span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}"> style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}">
<polyline points="6 9 12 15 18 9"/> <polyline points="6 9 12 15 18 9"/>
</svg> </svg>
</span> </span>
<span class="nav-tooltip">Podešavanja</span> <span class="nav-tooltip">Podešavanja</span>
</button> </button>
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "dozvole")}}otvoren{{end}}"> <div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-pdv-stope") (eq .Stranica "dozvole")}}otvoren{{end}}">
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}"> <a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
<span>Opšte</span> <span>Opšte</span>
@@ -175,6 +175,11 @@
<span>Sistem</span> <span>Sistem</span>
<span class="nav-tooltip">Sistem</span> <span class="nav-tooltip">Sistem</span>
</a> </a>
<a href="/admin/podesavanja/pdv-stope" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-pdv-stope"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="5" x2="5" y2="19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/></svg>
<span>PDV stope</span>
<span class="nav-tooltip">PDV stope</span>
</a>
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}} {{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
<a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}"> <a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
+90
View File
@@ -0,0 +1,90 @@
{{template "base" .}}
{{define "naslov"}}Podešavanja — PDV stope — NTech{{end}}
{{define "sadrzaj"}}
<div class="stranica-stack" style="width:100%;max-width:100%;">
<!-- postojeće stope: svaki red je forma za izmenu + zasebna forma za arhiviranje -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="5" x2="5" y2="19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Šifarnik PDV stopa</span>
</div>
<div class="pomocni-tekst" style="font-size:12px;margin-bottom:14px;">
Stope se koriste u kalkulaciji cena i PDV evidenciji. Stopa koja je negde upotrebljena se ne briše — arhivira se (nestaje iz padajućih lista, a postojeći zapisi ostaju ispravni).
</div>
{{if not .Stope}}
<div class="pomocni-tekst">Nema unetih PDV stopa.</div>
{{end}}
{{range .Stope}}
<div style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;padding:10px 0;border-bottom:0.5px solid var(--ivica);{{if not .Aktivna}}opacity:0.5;{{end}}">
<form method="POST" action="/podesavanja/pdv-stope/{{.ID}}/izmeni" style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;flex:1;">
<div style="flex:2;min-width:140px;">
<label class="polje-labela">Naziv</label>
<input type="text" name="naziv" value="{{.Naziv}}" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<div style="width:80px;">
<label class="polje-labela">Stopa %</label>
<input type="text" name="stopa" value="{{printf "%g" .Stopa}}" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<div style="width:150px;">
<label class="polje-labela">Vrsta</label>
<select name="oznaka" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
<option value="opsta" {{if eq .Oznaka "opsta"}}selected{{end}}>Opšta</option>
<option value="posebna" {{if eq .Oznaka "posebna"}}selected{{end}}>Posebna</option>
<option value="oslobodjeno" {{if eq .Oznaka "oslobodjeno"}}selected{{end}}>Oslobođeno</option>
</select>
</div>
<div style="width:80px;">
<label class="polje-labela">Redosled</label>
<input type="text" name="redosled" value="{{.Redosled}}" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<button type="submit" style="padding:7px 14px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">Sačuvaj</button>
</form>
<form method="POST" action="/podesavanja/pdv-stope/{{.ID}}/aktivnost">
<button type="submit" style="padding:7px 14px;background:var(--sb-aktivan);color:var(--tekst-jak);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;cursor:pointer;white-space:nowrap;">{{if .Aktivna}}Arhiviraj{{else}}Vrati{{end}}</button>
</form>
</div>
{{end}}
</div>
<!-- dodavanje nove stope -->
<form method="POST" action="/podesavanja/pdv-stope/dodaj">
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Nova stopa</span>
</div>
<div style="display:flex;gap:8px;align-items:flex-end;flex-wrap:wrap;">
<div style="flex:2;min-width:140px;">
<label class="polje-labela">Naziv</label>
<input type="text" name="naziv" placeholder="npr. Opšta stopa" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<div style="width:80px;">
<label class="polje-labela">Stopa %</label>
<input type="text" name="stopa" placeholder="20" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<div style="width:150px;">
<label class="polje-labela">Vrsta</label>
<select name="oznaka" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
<option value="opsta">Opšta</option>
<option value="posebna">Posebna</option>
<option value="oslobodjeno">Oslobođeno</option>
</select>
</div>
<div style="width:80px;">
<label class="polje-labela">Redosled</label>
<input type="text" name="redosled" placeholder="0" style="width:100%;padding:7px 10px;border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;background:var(--pozadina);color:var(--tekst-glavni);outline:none;">
</div>
<button type="submit" style="padding:7px 16px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap;">Dodaj</button>
</div>
</div>
</form>
</div>
{{end}}