Refaktoring: uklanjanje globalne teme i app pozadine, dozvole u podešavanja, UI ispravke

- Uklonjena globalna tema i pozadinska slika aplikacije (ostala samo lična pozadina po korisniku)
- Uklonjena animacija treperenja pozadine pri navigaciji; dodat sessionStorage za instant prikaz
- Dozvole premeštene iz sidebar-a u Podešavanja → Sistem; vidljive i adminu (samo Radnik kolona)
- Admin može menjati samo dozvole uloge Radnik, superadmin menja i Radnik i Admin
- Zatamnjivanje kartice NTech na stranici prijave — novi slider u Podešavanja → Izgled
- Upozorenje na dashboard-u (kritične zalihe) — popravljen kontrast boje
This commit is contained in:
2026-06-06 21:07:01 +02:00
parent 5d94ea34cf
commit 8cec26a03f
22 changed files with 269 additions and 888 deletions
+1 -6
View File
@@ -156,14 +156,9 @@ func main() {
r.Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu)
r.Post("/podesavanja/login-pozadina/ukloni", h.UkloniLoginPozadinu)
r.Post("/podesavanja/login-pozadina/stilovi", h.SacuvajLoginPozadinaStilove)
r.Post("/podesavanja/app-pozadina", h.OtpremiAppPozadinu)
r.Post("/podesavanja/app-pozadina/ukloni", h.UkloniAppPozadinu)
r.Post("/podesavanja/app-pozadina/stilovi", h.SacuvajAppPozadinaStilove)
r.Get("/podesavanja/backup", h.BackupBaze)
r.Post("/podesavanja/backup/vrati", h.VratiBackup)
r.Get("/tema/{tema}", h.PromeniTemu)
r.Post("/podesavanja/tema", h.PromeniGlobalnuTemu)
r.Post("/podesavanja/tema/globalno", h.PrimeniGlobalnuTemu)
r.Get("/magacin", h.Magacin)
r.Get("/magacin/novi", h.NoviArtikal)
r.Post("/magacin/novi", h.SacuvajArtikal)
+1 -1
View File
@@ -87,7 +87,7 @@ type KorisniciRepository interface {
PromeniLozinku(ctx context.Context, id int64, hash string) error
SacuvajTotpTajnu(ctx context.Context, id int64, tajna string) error
SacuvajLokalnuTemu(ctx context.Context, id int64, lokalnaTema string, koristi bool) error
SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine string) error
SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine, glassOpacity string) error
PostojiIjedan(ctx context.Context) (bool, error)
Obrisi(ctx context.Context, id int64) error
}
+21 -12
View File
@@ -31,17 +31,19 @@ func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme
k := &model.Korisnik{}
var aktivan, koristiLokalnuTemu int
var totpTajna, lokalnaTema sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
var datumKreiranja time.Time
err := r.db.QueryRowContext(ctx,
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''),
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0')
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10')
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme).
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &totpTajna,
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine)
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
&lokalnaPozadinaGlassOpacity)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoImenu: %w", err)
}
@@ -54,6 +56,7 @@ func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
return k, nil
}
@@ -61,17 +64,19 @@ func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model
k := &model.Korisnik{}
var aktivan, koristiLokalnuTemu int
var lokalnaTema sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
var datumKreiranja time.Time
err := r.db.QueryRowContext(ctx,
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''),
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0')
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10')
FROM korisnici WHERE id = ?`, id).
Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine)
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
&lokalnaPozadinaGlassOpacity)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoID: %w", err)
}
@@ -83,6 +88,7 @@ func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
return k, nil
}
@@ -91,7 +97,8 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
`SELECT id, korisnicko_ime, lozinka_hash, uloga, aktivan, COALESCE(totp_tajna, ''),
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0')
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10')
FROM korisnici ORDER BY datum_kreiranja ASC`)
if err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
@@ -102,11 +109,12 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
var k model.Korisnik
var aktivan, koristiLokalnuTemu int
var lokalnaTema sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine sql.NullString
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
var datumKreiranja time.Time
if err := rows.Scan(&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine); err != nil {
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur, &lokalnaPozadinaBlurPozadine,
&lokalnaPozadinaGlassOpacity); err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
}
k.Aktivan = aktivan == 1
@@ -117,15 +125,16 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
lista = append(lista, k)
}
return lista, nil
}
func (r *sqliteKorisniciRepo) SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine string) error {
func (r *sqliteKorisniciRepo) SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine, glassOpacity string) error {
_, err := r.db.ExecContext(ctx,
`UPDATE korisnici SET lokalna_pozadina = ?, lokalna_pozadina_opacity = ?, lokalna_pozadina_blur = ?, lokalna_pozadina_blur_pozadine = ? WHERE id = ?`,
pozadina, opacity, blur, blurPozadine, id)
`UPDATE korisnici SET lokalna_pozadina = ?, lokalna_pozadina_opacity = ?, lokalna_pozadina_blur = ?, lokalna_pozadina_blur_pozadine = ?, lokalna_pozadina_glass_opacity = ? WHERE id = ?`,
pozadina, opacity, blur, blurPozadine, glassOpacity, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.SacuvajLokalnuPozadinu: %w", err)
}
+16 -8
View File
@@ -33,19 +33,17 @@ type podaciAdminProfil struct {
TotpAktivan bool
LokalnaTema string
KoristiLokalnuTemu bool
GlobalnaTema string
}
type podaciProfilTema struct {
model.PodaciStranice
LokalnaTema string
KoristiLokalnuTemu bool
KoristiGlobalnuTemu bool // !KoristiLokalnuTemu — za Alpine.js svič "Koristi globalnu temu"
GlobalnaTema string
LokalnaPozadina string
LokalnaPozadinaOpacity string
LokalnaPozadinaBlur string
LokalnaPozadinaBlurPozadine string
LokalnaPozadinaGlassOpacity string
}
// AdminKorisnici prikazuje listu korisnika
@@ -304,7 +302,6 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) {
TotpAktivan: svezi.TotpTajna != "",
LokalnaTema: svezi.LokalnaTema,
KoristiLokalnuTemu: svezi.KoristiLokalnuTemu,
GlobalnaTema: podesavanja["globalna_tema"],
})
}
@@ -561,13 +558,20 @@ func (h *Handler) AdminDozvole(w http.ResponseWriter, r *http.Request) {
ps.Stranica = "dozvole"
ps.NaslovStranice = "Dozvole"
dozvoleAdmin := map[string]bool{}
dozvoleSuperadmin := map[string]bool{}
if k.Uloga == "superadmin" {
dozvoleAdmin = h.DozvoleRepo.SveDozvole(r.Context(), "admin")
dozvoleSuperadmin = h.DozvoleRepo.SveDozvole(r.Context(), "superadmin")
}
h.renderujTemplate(w, "admin_dozvole", podaciAdminDozvole{
PodaciStranice: ps,
Korisnici: lista,
TrenutniID: k.ID,
DozvoleRadnik: h.DozvoleRepo.SveDozvole(r.Context(), "radnik"),
DozvoleAdmin: h.DozvoleRepo.SveDozvole(r.Context(), "admin"),
DozvoleSuperadmin: h.DozvoleRepo.SveDozvole(r.Context(), "superadmin"),
DozvoleAdmin: dozvoleAdmin,
DozvoleSuperadmin: dozvoleSuperadmin,
})
}
@@ -644,8 +648,12 @@ func (h *Handler) AdminDozvoleSacuvaj(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
return
}
// čuvamo dozvole samo za radnik i admin — superadmin uvek ima sve
for _, uloga := range []string{"radnik", "admin"} {
// čuvamo dozvole: superadmin menja radnik i admin, admin menja samo radnik
uloge := []string{"radnik", "admin"}
if k.Uloga != "superadmin" {
uloge = []string{"radnik"}
}
for _, uloga := range uloge {
for _, akcija := range middleware.SveAkcije() {
kljuc := uloga + "__" + akcija
dozvoljeno := r.FormValue(kljuc) == "on"
+10 -46
View File
@@ -74,15 +74,8 @@ func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) {
// popuniPodaciStranice popunjava zajednička polja stranice uključujući prijavljenog korisnika
func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]string) model.PodaciStranice {
// redosled prioriteta teme: pozadinska slika → lokalna → globalna → fallback
globalnaTema := podesavanja["globalna_tema"]
if globalnaTema == "" {
globalnaTema = podesavanja["tema"]
}
if globalnaTema == "" {
globalnaTema = "tamna"
}
tema := globalnaTema
// podrazumevana tema je tamna; korisnik može imati svoju lokalnu temu
tema := "tamna"
ps := model.PodaciStranice{
Tema: tema,
@@ -99,8 +92,8 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
ps.KorisnikIme = k.KorisnickoIme
ps.KorisnikUloga = k.Uloga
ps.Dozvole = h.DozvoleRepo.SveDozvole(r.Context(), k.Uloga)
// lokalna tema korisnika
if k.KoristiLokalnuTemu && k.LokalnaTema != "" {
// lokalna tema korisnika — primenjuje se uvek kada je postavljena, bez obzira na KoristiLokalnuTemu
if k.LokalnaTema != "" {
ps.Tema = k.LokalnaTema
}
}
@@ -108,10 +101,10 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
ps.Flash = middleware.GetFlash(r, h.DB)
// logika pozadine:
// - lična pozadina (samo kada je lokalni režim aktivan) → zamenjuje globalnu
// - lična pozadina → uvek se prikazuje i forsira tamnu temu, bez obzira na KoristiLokalnuTemu
// - globalna pozadina → prikazuje se svima koji nemaju ličnu
// KoristiLokalnuTemu utiče na izbor tamne/svetle teme, ne na vidljivost pozadine
if korisnik != nil && korisnik.KoristiLokalnuTemu && korisnik.LokalnaPozadina != "" {
// KoristiLokalnuTemu i LokalnaTema važe samo kada nema lične pozadine
if korisnik != nil && korisnik.LokalnaPozadina != "" {
ps.AppPozadina = korisnik.LokalnaPozadina
ps.Tema = "tamna"
ps.AppPozadinaOpacity = korisnik.LokalnaPozadinaOpacity
@@ -126,38 +119,9 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
if ps.AppPozadinaBlurPozadine == "" {
ps.AppPozadinaBlurPozadine = "0"
}
} else {
ps.AppPozadina = podesavanja["app_pozadina"]
if ps.AppPozadina != "" {
// globalna pozadina forsira tamnu temu, osim ako korisnik ima aktivnu lokalnu temu
if korisnik == nil || !korisnik.KoristiLokalnuTemu {
ps.Tema = "tamna"
}
ps.AppPozadinaOpacity = podesavanja["app_pozadina_opacity"]
if ps.AppPozadinaOpacity == "" {
ps.AppPozadinaOpacity = "50"
}
ps.AppPozadinaBlur = podesavanja["app_pozadina_blur"]
if ps.AppPozadinaBlur == "" {
ps.AppPozadinaBlur = "12"
}
ps.AppPozadinaBlurPozadine = podesavanja["app_pozadina_blur_pozadine"]
if ps.AppPozadinaBlurPozadine == "" {
ps.AppPozadinaBlurPozadine = "0"
}
} else {
ps.AppPozadinaOpacity = podesavanja["app_pozadina_opacity"]
if ps.AppPozadinaOpacity == "" {
ps.AppPozadinaOpacity = "50"
}
ps.AppPozadinaBlur = podesavanja["app_pozadina_blur"]
if ps.AppPozadinaBlur == "" {
ps.AppPozadinaBlur = "12"
}
ps.AppPozadinaBlurPozadine = podesavanja["app_pozadina_blur_pozadine"]
if ps.AppPozadinaBlurPozadine == "" {
ps.AppPozadinaBlurPozadine = "0"
}
ps.AppPozadinaGlassOpacity = korisnik.LokalnaPozadinaGlassOpacity
if ps.AppPozadinaGlassOpacity == "" {
ps.AppPozadinaGlassOpacity = "10"
}
}
+26 -350
View File
@@ -18,8 +18,6 @@ import (
ntechsqlite "ntech/internal/db/sqlite"
"ntech/internal/middleware"
"ntech/internal/model"
"github.com/go-chi/chi/v5"
)
// PodaciPodesavanja su podaci za stranicu podešavanja
@@ -32,20 +30,16 @@ type PodaciPodesavanja struct {
PIB string
LogoTip string
LogoPutanja string
GlobalnaTema string // stvarna vrednost iz podešavanja (ne senči PodaciStranice.Tema)
Sacuvano bool
Verzija string
LogoGreska string
BackupVracen bool
Backupi []BackupInfo
LoginPozadina string
LoginPozadinaOpacity string
LoginPozadinaBlurPozadine string
LoginPozadinaBlurKartice string
AppPozadina string
AppPozadinaOpacity string
AppPozadinaBlur string
AppPozadinaBlurPozadine string
LoginPozadina string
LoginPozadinaOpacity string
LoginPozadinaBlurPozadine string
LoginPozadinaBlurKartice string
LoginPozadinaZatamnjenjeKartice string
}
// BackupInfo opisuje jedan backup fajl
@@ -83,15 +77,6 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
PIB: podesavanja["pib"],
LogoTip: podesavanja["logo_tip"],
LogoPutanja: podesavanja["logo_putanja"],
GlobalnaTema: func() string {
if v := podesavanja["globalna_tema"]; v != "" {
return v
}
if v := podesavanja["tema"]; v != "" {
return v
}
return "tamna"
}(),
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno",
Verzija: h.Verzija,
@@ -113,19 +98,8 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
if v == "" { return "12" }
return v
}(),
AppPozadina: podesavanja["app_pozadina"],
AppPozadinaOpacity: func() string {
v := podesavanja["app_pozadina_opacity"]
if v == "" { return "50" }
return v
}(),
AppPozadinaBlur: func() string {
v := podesavanja["app_pozadina_blur"]
if v == "" { return "12" }
return v
}(),
AppPozadinaBlurPozadine: func() string {
v := podesavanja["app_pozadina_blur_pozadine"]
LoginPozadinaZatamnjenjeKartice: func() string {
v := podesavanja["login_pozadina_zatamnjenje_kartice"]
if v == "" { return "0" }
return v
}(),
@@ -260,14 +234,12 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
}
polja := map[string]string{
"naziv_firme": r.FormValue("naziv_firme"),
"podnazlov": r.FormValue("podnazlov"),
"adresa": r.FormValue("adresa"),
"telefon": r.FormValue("telefon"),
"pib": r.FormValue("pib"),
"logo_tip": r.FormValue("logo_tip"),
"tema": r.FormValue("tema"),
"globalna_tema": r.FormValue("globalna_tema"),
"naziv_firme": r.FormValue("naziv_firme"),
"podnazlov": r.FormValue("podnazlov"),
"adresa": r.FormValue("adresa"),
"telefon": r.FormValue("telefon"),
"pib": r.FormValue("pib"),
"logo_tip": r.FormValue("logo_tip"),
}
for kljuc, vrednost := range polja {
@@ -539,210 +511,6 @@ func (h *Handler) UkloniLoginPozadinu(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
}
// OtpremiAppPozadinu prima multipart upload slike i čuva je kao pozadinsku sliku aplikacije
func (h *Handler) OtpremiAppPozadinu(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if k == nil || !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "podesavanja.app_pozadina") {
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 5<<20+4096)
if err := r.ParseMultipartForm(5 << 20); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Fajl je prevelik (maksimum 5 MB).")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
fajl, zaglavlje, err := r.FormFile("app_pozadina")
if err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Nije odabran fajl.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
defer fajl.Close()
if zaglavlje.Size > 5<<20 {
middleware.SetFlash(w, r, h.DB, "greska", "Fajl je prevelik (maksimum 5 MB).")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
ext := strings.ToLower(filepath.Ext(zaglavlje.Filename))
dozvoljenoExt := map[string]string{
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".webp": "image/webp",
}
ocekivaniMime, ok := dozvoljenoExt[ext]
if !ok {
middleware.SetFlash(w, r, h.DB, "greska", "Dozvoljeni formati su JPG, PNG i WebP.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
buf := make([]byte, 512)
n, _ := fajl.Read(buf)
stvarniMime := http.DetectContentType(buf[:n])
if !strings.HasPrefix(stvarniMime, ocekivaniMime) {
middleware.SetFlash(w, r, h.DB, "greska", "Sadržaj fajla ne odgovara odabranoj ekstenziji.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
if _, err := fajl.Seek(0, io.SeekStart); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri obradi fajla.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
// briše staru app pozadinu sa diska ako postoji
staraPodesavanja, _ := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
if stara := staraPodesavanja["app_pozadina"]; stara != "" {
deoBezverzije := strings.Split(stara, "?")[0]
staroIme := filepath.Base(deoBezverzije)
os.Remove(filepath.Join("web/static/uploads", staroIme))
}
novoIme, err := generisiImeUploada(ext)
if err != nil {
log.Printf("upload app pozadine: greška pri generisanju imena: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
odrediste := filepath.Join("web/static/uploads", novoIme)
dst, err := os.Create(odrediste)
if err != nil {
log.Printf("upload app pozadine: ne mogu kreirati fajl: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
defer dst.Close()
if _, err := io.Copy(dst, fajl); err != nil {
log.Printf("upload app pozadine: greška pri kopiranju: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
putanja := fmt.Sprintf("/static/uploads/%s?v=%d", novoIme, time.Now().Unix())
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "app_pozadina", putanja); err != nil {
log.Printf("upload app pozadine: greška pri čuvanju putanje: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
// zapamti trenutnu globalnu temu pre nego što je forsiramo na tamnu
trenutnaTema := staraPodesavanja["globalna_tema"]
if trenutnaTema == "" {
trenutnaTema = staraPodesavanja["tema"]
}
if trenutnaTema == "" {
trenutnaTema = "tamna"
}
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "tema_pre_slike", trenutnaTema)
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "globalna_tema", "tamna")
middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika aplikacije je uspešno otpremljena.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
}
// UkloniAppPozadinu briše pozadinsku sliku aplikacije sa diska i iz podešavanja
func (h *Handler) UkloniAppPozadinu(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if k == nil || !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "podesavanja.app_pozadina") {
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
return
}
podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
if err == nil {
if stara := podesavanja["app_pozadina"]; stara != "" {
deoBezverzije := strings.Split(stara, "?")[0]
staroIme := filepath.Base(deoBezverzije)
os.Remove(filepath.Join("web/static/uploads", staroIme))
}
}
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "app_pozadina", ""); err != nil {
log.Printf("ukloni app pozadinu: greška pri čuvanju: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju slike.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
// vrati temu koja je bila aktivna pre dodavanja slike
if err == nil {
temaPreSlike := podesavanja["tema_pre_slike"]
if temaPreSlike == "" {
temaPreSlike = "tamna"
}
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "globalna_tema", temaPreSlike)
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "tema_pre_slike", "")
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika aplikacije je uklonjena.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
}
// SacuvajAppPozadinaStilove čuva vrednosti zamućenja i prozirnosti pozadine aplikacije
func (h *Handler) SacuvajAppPozadinaStilove(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if k == nil || !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "podesavanja.app_pozadina") {
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
return
}
if err := r.ParseForm(); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čitanju forme.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
blurStr := r.FormValue("blur")
blurPozadineStr := r.FormValue("blur_pozadine")
opacityStr := r.FormValue("opacity")
blurVal, err := strconv.Atoi(blurStr)
if err != nil || blurVal < 0 || blurVal > 20 {
middleware.SetFlash(w, r, h.DB, "greska", "Neispravna vrednost zamućenja stakla.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
blurPozadineVal, err := strconv.Atoi(blurPozadineStr)
if err != nil || blurPozadineVal < 0 || blurPozadineVal > 20 {
middleware.SetFlash(w, r, h.DB, "greska", "Neispravna vrednost zamućenja pozadine.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
opacityVal, err := strconv.Atoi(opacityStr)
if err != nil || opacityVal < 0 || opacityVal > 80 {
middleware.SetFlash(w, r, h.DB, "greska", "Neispravna vrednost prozirnosti.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
for kljuc, vrednost := range map[string]string{
"app_pozadina_blur": blurStr,
"app_pozadina_blur_pozadine": blurPozadineStr,
"app_pozadina_opacity": opacityStr,
} {
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil {
log.Printf("stilovi app pozadine: greška pri čuvanju %s: %v", kljuc, err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Izgled pozadine aplikacije je sačuvan.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
}
// SacuvajLoginPozadinaStilove čuva vrednosti zamućenja i prozirnosti pozadine login stranice
func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
@@ -759,6 +527,7 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req
blurPozadineStr := r.FormValue("blur_pozadine")
blurKarticeStr := r.FormValue("blur_kartice")
opacityStr := r.FormValue("opacity")
zatamnjenjeKarticeStr := r.FormValue("zatamnjenje_kartice")
blurPozadineVal, err := strconv.Atoi(blurPozadineStr)
if err != nil || blurPozadineVal < 0 || blurPozadineVal > 20 {
@@ -778,11 +547,18 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
zatamnjenjeKarticeVal, err := strconv.Atoi(zatamnjenjeKarticeStr)
if err != nil || zatamnjenjeKarticeVal < 0 || zatamnjenjeKarticeVal > 80 {
middleware.SetFlash(w, r, h.DB, "greska", "Neispravna vrednost zatamnjivanja kartice.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
return
}
for kljuc, vrednost := range map[string]string{
"login_pozadina_blur_pozadine": blurPozadineStr,
"login_pozadina_blur_kartice": blurKarticeStr,
"login_pozadina_opacity": opacityStr,
"login_pozadina_blur_pozadine": blurPozadineStr,
"login_pozadina_blur_kartice": blurKarticeStr,
"login_pozadina_opacity": opacityStr,
"login_pozadina_zatamnjenje_kartice": zatamnjenjeKarticeStr,
} {
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, kljuc, vrednost); err != nil {
log.Printf("stilovi login pozadine: greška pri čuvanju %s: %v", kljuc, err)
@@ -814,15 +590,6 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
PIB: podesavanja["pib"],
LogoTip: podesavanja["logo_tip"],
LogoPutanja: podesavanja["logo_putanja"],
GlobalnaTema: func() string {
if v := podesavanja["globalna_tema"]; v != "" {
return v
}
if v := podesavanja["tema"]; v != "" {
return v
}
return "tamna"
}(),
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
BackupVracen: r.URL.Query().Get("sacuvano") == "vraceno",
Verzija: h.Verzija,
@@ -847,21 +614,8 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
}
return "12"
}(),
AppPozadina: podesavanja["app_pozadina"],
AppPozadinaOpacity: func() string {
if v := podesavanja["app_pozadina_opacity"]; v != "" {
return v
}
return "50"
}(),
AppPozadinaBlur: func() string {
if v := podesavanja["app_pozadina_blur"]; v != "" {
return v
}
return "12"
}(),
AppPozadinaBlurPozadine: func() string {
if v := podesavanja["app_pozadina_blur_pozadine"]; v != "" {
LoginPozadinaZatamnjenjeKartice: func() string {
if v := podesavanja["login_pozadina_zatamnjenje_kartice"]; v != "" {
return v
}
return "0"
@@ -914,81 +668,3 @@ func (h *Handler) PodesavanjaSistem(w http.ResponseWriter, r *http.Request) {
h.renderujTemplate(w, "podesavanja_sistem", podaci)
}
// PromeniTemu menja aktivnu temu i vraća na prethodnu stranicu (stara GET ruta, zadržana za kompatibilnost)
func (h *Handler) PromeniTemu(w http.ResponseWriter, r *http.Request) {
tema := chi.URLParam(r, "tema")
validne := map[string]bool{"tamna": true, "svetla": true}
if !validne[tema] {
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
return
}
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "globalna_tema", tema)
referer := r.Header.Get("Referer")
if referer == "" {
referer = "/dashboard"
}
http.Redirect(w, r, referer, http.StatusSeeOther)
}
// PromeniGlobalnuTemu prima POST sa poljem "tema" i čuva u globalna_tema u podešavanjima
func (h *Handler) PromeniGlobalnuTemu(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
return
}
tema := r.FormValue("tema")
validne := map[string]bool{"tamna": true, "svetla": true}
if !validne[tema] {
tema = "tamna"
}
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "globalna_tema", tema); err != nil {
http.Error(w, "Greška pri promeni teme", http.StatusInternalServerError)
return
}
referer := r.Header.Get("Referer")
if referer == "" {
referer = "/dashboard"
}
http.Redirect(w, r, referer, http.StatusSeeOther)
}
// PrimeniGlobalnuTemu čuva globalnu temu i resetuje lokalne teme svih korisnika
func (h *Handler) PrimeniGlobalnuTemu(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context())
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.globalno") {
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, "Greška pri čitanju forme", http.StatusBadRequest)
return
}
tema := r.FormValue("globalna_tema")
validne := map[string]bool{"tamna": true, "svetla": true}
if !validne[tema] {
tema = "tamna"
}
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "globalna_tema", tema); err != nil {
http.Error(w, "Greška pri čuvanju teme", http.StatusInternalServerError)
return
}
// poništi lokalne teme svih korisnika — tema se primenjuje globalno
if _, err := h.DB.ExecContext(r.Context(),
"UPDATE korisnici SET koristi_lokalnu_temu = 0, lokalna_tema = ''",
); err != nil {
log.Printf("PrimeniGlobalnuTemu: reset lokalnih tema: %v", err)
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Globalna tema primenjena na sve korisnike.")
http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther)
}
+11 -6
View File
@@ -36,6 +36,7 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) {
loginOpacity := "50"
loginBlurPozadine := "0"
loginBlurKartice := "12"
loginZatamnjenjeKartice := "0"
if podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(context.Background(), h.DB); err == nil {
loginPozadina = podesavanja["login_pozadina"]
if v := podesavanja["login_pozadina_opacity"]; v != "" {
@@ -47,15 +48,19 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) {
if v := podesavanja["login_pozadina_blur_kartice"]; v != "" {
loginBlurKartice = v
}
if v := podesavanja["login_pozadina_zatamnjenje_kartice"]; v != "" {
loginZatamnjenjeKartice = v
}
}
h.renderujStandalone(w, "prijava", map[string]any{
"Greska": greska,
"CsrfToken": middleware.CsrfToken(r.Context()),
"LoginPozadina": loginPozadina,
"LoginPozadinaOpacity": loginOpacity,
"LoginPozadinaBlurPozadine": loginBlurPozadine,
"LoginPozadinaBlurKartice": loginBlurKartice,
"Greska": greska,
"CsrfToken": middleware.CsrfToken(r.Context()),
"LoginPozadina": loginPozadina,
"LoginPozadinaOpacity": loginOpacity,
"LoginPozadinaBlurPozadine": loginBlurPozadine,
"LoginPozadinaBlurKartice": loginBlurKartice,
"LoginPozadinaZatamnjenjeKartice": loginZatamnjenjeKartice,
})
}
+15 -5
View File
@@ -42,12 +42,11 @@ func (h *Handler) ProfilTema(w http.ResponseWriter, r *http.Request) {
PodaciStranice: ps,
LokalnaTema: svezi.LokalnaTema,
KoristiLokalnuTemu: svezi.KoristiLokalnuTemu,
KoristiGlobalnuTemu: !svezi.KoristiLokalnuTemu,
GlobalnaTema: podesavanja["globalna_tema"],
LokalnaPozadina: svezi.LokalnaPozadina,
LokalnaPozadinaOpacity: svezi.LokalnaPozadinaOpacity,
LokalnaPozadinaBlur: svezi.LokalnaPozadinaBlur,
LokalnaPozadinaBlurPozadine: svezi.LokalnaPozadinaBlurPozadine,
LokalnaPozadinaGlassOpacity: svezi.LokalnaPozadinaGlassOpacity,
}
if podaci.LokalnaPozadinaOpacity == "" {
podaci.LokalnaPozadinaOpacity = "50"
@@ -58,6 +57,9 @@ func (h *Handler) ProfilTema(w http.ResponseWriter, r *http.Request) {
if podaci.LokalnaPozadinaBlurPozadine == "" {
podaci.LokalnaPozadinaBlurPozadine = "0"
}
if podaci.LokalnaPozadinaGlassOpacity == "" {
podaci.LokalnaPozadinaGlassOpacity = "10"
}
h.renderujTemplate(w, "profil_tema", podaci)
}
@@ -149,6 +151,7 @@ func (h *Handler) ProfilOtpremiPozadinu(w http.ResponseWriter, r *http.Request)
opacity := "50"
blur := "12"
blurPozadine := "0"
glassOpacity := "10"
if svezi != nil {
if svezi.LokalnaPozadinaOpacity != "" {
opacity = svezi.LokalnaPozadinaOpacity
@@ -159,9 +162,12 @@ func (h *Handler) ProfilOtpremiPozadinu(w http.ResponseWriter, r *http.Request)
if svezi.LokalnaPozadinaBlurPozadine != "" {
blurPozadine = svezi.LokalnaPozadinaBlurPozadine
}
if svezi.LokalnaPozadinaGlassOpacity != "" {
glassOpacity = svezi.LokalnaPozadinaGlassOpacity
}
}
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, putanja, opacity, blur, blurPozadine); err != nil {
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, putanja, opacity, blur, blurPozadine, glassOpacity); err != nil {
log.Printf("ProfilOtpremiPozadinu: greška pri čuvanju: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
@@ -186,7 +192,7 @@ func (h *Handler) ProfilUkloniPozadinu(w http.ResponseWriter, r *http.Request) {
os.Remove(filepath.Join("web/static/uploads", filepath.Base(deo)))
}
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, "", "50", "12", "0"); err != nil {
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, "", "50", "12", "0", "10"); err != nil {
log.Printf("ProfilUkloniPozadinu: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju slike.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
@@ -220,6 +226,7 @@ func (h *Handler) ProfilSacuvajPozadinuStilove(w http.ResponseWriter, r *http.Re
opacity := r.FormValue("lokalna_pozadina_opacity")
blur := r.FormValue("lokalna_pozadina_blur")
blurPozadineSt := r.FormValue("lokalna_pozadina_blur_pozadine")
glassOpacitySt := r.FormValue("lokalna_pozadina_glass_opacity")
if opacity == "" {
opacity = "50"
}
@@ -229,8 +236,11 @@ func (h *Handler) ProfilSacuvajPozadinuStilove(w http.ResponseWriter, r *http.Re
if blurPozadineSt == "" {
blurPozadineSt = "0"
}
if glassOpacitySt == "" {
glassOpacitySt = "10"
}
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, pozadina, opacity, blur, blurPozadineSt); err != nil {
if err := h.KorisniciRepo.SacuvajLokalnuPozadinu(r.Context(), k.ID, pozadina, opacity, blur, blurPozadineSt, glassOpacitySt); err != nil {
log.Printf("ProfilSacuvajPozadinuStilove: %v", err)
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.")
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
+1
View File
@@ -17,6 +17,7 @@ type Korisnik struct {
LokalnaPozadinaOpacity string
LokalnaPozadinaBlur string
LokalnaPozadinaBlurPozadine string
LokalnaPozadinaGlassOpacity string
}
// Sesija predstavlja aktivnu sesiju prijavljenog korisnika
+1
View File
@@ -49,6 +49,7 @@ type PodaciStranice struct {
AppPozadinaOpacity string // vrednost 0-80 (% overlay zatamnjivanja)
AppPozadinaBlur string // vrednost 0-20 (px backdrop-filter blur na elementima)
AppPozadinaBlurPozadine string // vrednost 0-20 (px filter blur na pozadinskoj slici)
AppPozadinaGlassOpacity string // vrednost 0-80 (% zatamnjivanje glass elemenata) — samo za ličnu pozadinu
}
// PodaciDashboarda su podaci specifični za dashboard stranicu
@@ -0,0 +1 @@
ALTER TABLE korisnici ADD COLUMN lokalna_pozadina_glass_opacity TEXT DEFAULT '10';
+1
View File
@@ -0,0 +1 @@
DELETE FROM podesavanja WHERE kljuc IN ('globalna_tema', 'tema', 'tema_pre_slike');
+1
View File
@@ -0,0 +1 @@
DELETE FROM podesavanja WHERE kljuc IN ('app_pozadina', 'app_pozadina_opacity', 'app_pozadina_blur', 'app_pozadina_blur_pozadine');
+8 -9
View File
@@ -132,20 +132,12 @@
</a>
{{end}}
{{if eq .KorisnikUloga "superadmin"}}
<a href="/admin/dozvole" class="nav-stavka {{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>
<span>Dozvole</span>
<span class="nav-tooltip">Dozvole</span>
</a>
{{end}}
{{if ne .KorisnikUloga "radnik"}}
<div class="nav-separator"></div>
<div class="nav-oznaka">Sistem</div>
{{if index .Dozvole "podesavanja.pregled"}}
<div x-data="{ otvoren: {{if eq .Stranica "podesavanja"}}true{{else}}false{{end}} }">
<div x-data="{ otvoren: {{if or (eq .Stranica "podesavanja") (eq .Stranica "dozvole")}}true{{else}}false{{end}} }">
<button type="button" @click="otvoren = !otvoren"
class="nav-stavka {{if eq .Stranica "podesavanja"}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;">
@@ -175,6 +167,13 @@
<span>Sistem</span>
<span class="nav-tooltip">Sistem</span>
</a>
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
<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>
<span>Dozvole</span>
<span class="nav-tooltip">Dozvole</span>
</a>
{{end}}
</div>
</div>
{{end}}
-17
View File
@@ -12,23 +12,6 @@
<span class="topbar-naslov">{{.NaslovStranice}}</span>
{{if not .AppPozadina}}
<div class="topbar-teme" style="display:flex;align-items:center;gap:8px;">
<form method="POST" action="/podesavanja/tema" style="margin:0;padding:0;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="tema" value="tamna">
<button type="submit" class="tema-krug {{if eq .Tema "tamna"}}tema-krug-aktivan{{end}}"
style="background:#1a1d27;border:none;padding:0;cursor:pointer;" title="Tamna"></button>
</form>
<form method="POST" action="/podesavanja/tema" style="margin:0;padding:0;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="tema" value="svetla">
<button type="submit" class="tema-krug {{if eq .Tema "svetla"}}tema-krug-aktivan{{end}}"
style="background:#f0f2f5;border:1px solid #e2e6ed;padding:0;cursor:pointer;" title="Svetla"></button>
</form>
</div>
{{end}}
<div style="position:relative;" id="avatar-wrapper">
<div class="avatar-korisnik" id="avatar-dugme" style="cursor:pointer;" title="{{.Korisnik}}">
{{if .Korisnik}}{{slice .Korisnik 0 2}}{{else}}NT{{end}}
+55 -53
View File
@@ -42,290 +42,292 @@
<tr style="border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);min-width:200px;">Akcija</th>
<th style="padding:10px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:110px;">Radnik</th>
{{if eq .KorisnikUloga "superadmin"}}
<th style="padding:10px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:110px;">Admin</th>
{{end}}
</tr>
</thead>
<tbody>
<!-- Magacin -->
<tr class="matrica-modul"><td colspan="3">Magacin</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Magacin</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled artikala</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.pregled" {{if index .DozvoleRadnik "artikal.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.pregled" {{if index .DozvoleAdmin "artikal.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.pregled" {{if index .DozvoleAdmin "artikal.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje artikala</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.dodaj" {{if index .DozvoleRadnik "artikal.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.dodaj" {{if index .DozvoleAdmin "artikal.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.dodaj" {{if index .DozvoleAdmin "artikal.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena artikala</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.izmeni" {{if index .DozvoleRadnik "artikal.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.izmeni" {{if index .DozvoleAdmin "artikal.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.izmeni" {{if index .DozvoleAdmin "artikal.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje artikala</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.obrisi" {{if index .DozvoleRadnik "artikal.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.obrisi" {{if index .DozvoleAdmin "artikal.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.obrisi" {{if index .DozvoleAdmin "artikal.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Premeštanje artikala</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.premesti" {{if index .DozvoleRadnik "artikal.premesti"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.premesti" {{if index .DozvoleAdmin "artikal.premesti"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.premesti" {{if index .DozvoleAdmin "artikal.premesti"}}checked{{end}}></td>{{end}}
</tr>
<!-- Kategorije -->
<tr class="matrica-modul"><td colspan="3">Kategorije</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Kategorije</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled kategorija</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.pregled" {{if index .DozvoleRadnik "kategorija.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.pregled" {{if index .DozvoleAdmin "kategorija.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.pregled" {{if index .DozvoleAdmin "kategorija.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje kategorija</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.dodaj" {{if index .DozvoleRadnik "kategorija.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.dodaj" {{if index .DozvoleAdmin "kategorija.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.dodaj" {{if index .DozvoleAdmin "kategorija.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena kategorija</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.izmeni" {{if index .DozvoleRadnik "kategorija.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.izmeni" {{if index .DozvoleAdmin "kategorija.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.izmeni" {{if index .DozvoleAdmin "kategorija.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje kategorija</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.obrisi" {{if index .DozvoleRadnik "kategorija.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.obrisi" {{if index .DozvoleAdmin "kategorija.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.obrisi" {{if index .DozvoleAdmin "kategorija.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Nabavke -->
<tr class="matrica-modul"><td colspan="3">Nabavke</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Nabavke</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled nabavki</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__nabavka.pregled" {{if index .DozvoleRadnik "nabavka.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.pregled" {{if index .DozvoleAdmin "nabavka.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.pregled" {{if index .DozvoleAdmin "nabavka.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje nabavki</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__nabavka.dodaj" {{if index .DozvoleRadnik "nabavka.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.dodaj" {{if index .DozvoleAdmin "nabavka.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.dodaj" {{if index .DozvoleAdmin "nabavka.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje nabavki</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__nabavka.obrisi" {{if index .DozvoleRadnik "nabavka.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.obrisi" {{if index .DozvoleAdmin "nabavka.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__nabavka.obrisi" {{if index .DozvoleAdmin "nabavka.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Dobavljači -->
<tr class="matrica-modul"><td colspan="3">Dobavljači</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Dobavljači</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled dobavljača</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__dobavljac.pregled" {{if index .DozvoleRadnik "dobavljac.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.pregled" {{if index .DozvoleAdmin "dobavljac.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.pregled" {{if index .DozvoleAdmin "dobavljac.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje dobavljača</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__dobavljac.dodaj" {{if index .DozvoleRadnik "dobavljac.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.dodaj" {{if index .DozvoleAdmin "dobavljac.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.dodaj" {{if index .DozvoleAdmin "dobavljac.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena dobavljača</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__dobavljac.izmeni" {{if index .DozvoleRadnik "dobavljac.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.izmeni" {{if index .DozvoleAdmin "dobavljac.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.izmeni" {{if index .DozvoleAdmin "dobavljac.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje dobavljača</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__dobavljac.obrisi" {{if index .DozvoleRadnik "dobavljac.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.obrisi" {{if index .DozvoleAdmin "dobavljac.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__dobavljac.obrisi" {{if index .DozvoleAdmin "dobavljac.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Servis -->
<tr class="matrica-modul"><td colspan="3">Servis</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Servis</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled servisnih naloga</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__servis.pregled" {{if index .DozvoleRadnik "servis.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.pregled" {{if index .DozvoleAdmin "servis.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.pregled" {{if index .DozvoleAdmin "servis.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje servisnih naloga</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__servis.dodaj" {{if index .DozvoleRadnik "servis.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.dodaj" {{if index .DozvoleAdmin "servis.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.dodaj" {{if index .DozvoleAdmin "servis.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena servisnih naloga</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__servis.izmeni" {{if index .DozvoleRadnik "servis.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.izmeni" {{if index .DozvoleAdmin "servis.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.izmeni" {{if index .DozvoleAdmin "servis.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje servisnih naloga</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__servis.obrisi" {{if index .DozvoleRadnik "servis.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.obrisi" {{if index .DozvoleAdmin "servis.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__servis.obrisi" {{if index .DozvoleAdmin "servis.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Prodaja -->
<tr class="matrica-modul"><td colspan="3">Prodaja</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Prodaja</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled prodaje</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.pregled" {{if index .DozvoleRadnik "prodaja.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.pregled" {{if index .DozvoleAdmin "prodaja.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.pregled" {{if index .DozvoleAdmin "prodaja.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje prodaje</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.dodaj" {{if index .DozvoleRadnik "prodaja.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.dodaj" {{if index .DozvoleAdmin "prodaja.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.dodaj" {{if index .DozvoleAdmin "prodaja.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje prodaje</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.obrisi" {{if index .DozvoleRadnik "prodaja.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.obrisi" {{if index .DozvoleAdmin "prodaja.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.obrisi" {{if index .DozvoleAdmin "prodaja.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Klijenti -->
<tr class="matrica-modul"><td colspan="3">Klijenti</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Klijenti</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled klijenata</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__klijent.pregled" {{if index .DozvoleRadnik "klijent.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.pregled" {{if index .DozvoleAdmin "klijent.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.pregled" {{if index .DozvoleAdmin "klijent.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje klijenata</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__klijent.dodaj" {{if index .DozvoleRadnik "klijent.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.dodaj" {{if index .DozvoleAdmin "klijent.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.dodaj" {{if index .DozvoleAdmin "klijent.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena klijenata</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__klijent.izmeni" {{if index .DozvoleRadnik "klijent.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.izmeni" {{if index .DozvoleAdmin "klijent.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.izmeni" {{if index .DozvoleAdmin "klijent.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje klijenata</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__klijent.obrisi" {{if index .DozvoleRadnik "klijent.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.obrisi" {{if index .DozvoleAdmin "klijent.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__klijent.obrisi" {{if index .DozvoleAdmin "klijent.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Podsetnici -->
<tr class="matrica-modul"><td colspan="3">Podsetnici</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Podsetnici</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled podsetnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.pregled" {{if index .DozvoleRadnik "podsetnik.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.pregled" {{if index .DozvoleAdmin "podsetnik.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.pregled" {{if index .DozvoleAdmin "podsetnik.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje podsetnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.dodaj" {{if index .DozvoleRadnik "podsetnik.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.dodaj" {{if index .DozvoleAdmin "podsetnik.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.dodaj" {{if index .DozvoleAdmin "podsetnik.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena podsetnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.izmeni" {{if index .DozvoleRadnik "podsetnik.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.izmeni" {{if index .DozvoleAdmin "podsetnik.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.izmeni" {{if index .DozvoleAdmin "podsetnik.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje podsetnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.obrisi" {{if index .DozvoleRadnik "podsetnik.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.obrisi" {{if index .DozvoleAdmin "podsetnik.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.obrisi" {{if index .DozvoleAdmin "podsetnik.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<!-- Izveštaji -->
<tr class="matrica-modul"><td colspan="3">Izveštaji</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Izveštaji</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled izveštaja</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__izvestaj.pregled" {{if index .DozvoleRadnik "izvestaj.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__izvestaj.pregled" {{if index .DozvoleAdmin "izvestaj.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__izvestaj.pregled" {{if index .DozvoleAdmin "izvestaj.pregled"}}checked{{end}}></td>{{end}}
</tr>
<!-- Podešavanja -->
<tr class="matrica-modul"><td colspan="3">Podešavanja</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Podešavanja</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled podešavanja</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podesavanja.pregled" {{if index .DozvoleRadnik "podesavanja.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.pregled" {{if index .DozvoleAdmin "podesavanja.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.pregled" {{if index .DozvoleAdmin "podesavanja.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena podešavanja</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podesavanja.izmeni" {{if index .DozvoleRadnik "podesavanja.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.izmeni" {{if index .DozvoleAdmin "podesavanja.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.izmeni" {{if index .DozvoleAdmin "podesavanja.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<!-- Korisnici -->
<tr class="matrica-modul"><td colspan="3">Korisnici</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Korisnici</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled korisnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.pregled" {{if index .DozvoleRadnik "korisnik.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.pregled" {{if index .DozvoleAdmin "korisnik.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.pregled" {{if index .DozvoleAdmin "korisnik.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje korisnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.dodaj" {{if index .DozvoleRadnik "korisnik.dodaj"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.dodaj" {{if index .DozvoleAdmin "korisnik.dodaj"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.dodaj" {{if index .DozvoleAdmin "korisnik.dodaj"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena korisnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.izmeni" {{if index .DozvoleRadnik "korisnik.izmeni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.izmeni" {{if index .DozvoleAdmin "korisnik.izmeni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.izmeni" {{if index .DozvoleAdmin "korisnik.izmeni"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje korisnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.obrisi" {{if index .DozvoleRadnik "korisnik.obrisi"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.obrisi" {{if index .DozvoleAdmin "korisnik.obrisi"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.obrisi" {{if index .DozvoleAdmin "korisnik.obrisi"}}checked{{end}}></td>{{end}}
</tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Promena uloge korisnika</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.uloga" {{if index .DozvoleRadnik "korisnik.uloga"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.uloga" {{if index .DozvoleAdmin "korisnik.uloga"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.uloga" {{if index .DozvoleAdmin "korisnik.uloga"}}checked{{end}}></td>{{end}}
</tr>
<!-- Backup -->
<tr class="matrica-modul"><td colspan="3">Backup</td></tr>
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Backup</td></tr>
<tr style="border-bottom:0.5px solid var(--ivica);">
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled rezervnih kopija</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__backup.pregled" {{if index .DozvoleRadnik "backup.pregled"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__backup.pregled" {{if index .DozvoleAdmin "backup.pregled"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__backup.pregled" {{if index .DozvoleAdmin "backup.pregled"}}checked{{end}}></td>{{end}}
</tr>
<tr>
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pokretanje bekapa</td>
<td class="matrica-checkbox"><input type="checkbox" name="radnik__backup.pokreni" {{if index .DozvoleRadnik "backup.pokreni"}}checked{{end}}></td>
<td class="matrica-checkbox"><input type="checkbox" name="admin__backup.pokreni" {{if index .DozvoleAdmin "backup.pokreni"}}checked{{end}}></td>
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__backup.pokreni" {{if index .DozvoleAdmin "backup.pokreni"}}checked{{end}}></td>{{end}}
</tr>
+1 -1
View File
@@ -86,7 +86,7 @@
<div class="kartica dash-kartica animiraj">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
<span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Kritične zalihe</span>
<span style="font-size:11px;padding:2px 8px;border-radius:20px;background:#fffbeb;color:#b45309;font-weight:500;">Upozorenje</span>
<span style="font-size:11px;padding:2px 8px;border-radius:20px;background:rgba(220,38,38,0.15);color:#ef4444;font-weight:500;">Upozorenje</span>
</div>
{{range .KriticneZalihe}}
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
+15 -174
View File
@@ -198,136 +198,6 @@
<!-- ══════════════════════════════════════════════ IZGLED ══ -->
<div id="izgled" style="scroll-margin-top:20px;">
<!-- pozadinska slika aplikacije -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<!-- naslov sekcije -->
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
Pozadinska slika aplikacije
</div>
<!-- thumbnail trenutne slike + brisanje -->
{{if .AppPozadina}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
<img src="{{.AppPozadina}}" alt="Trenutna pozadina"
style="width:120px;height:70px;object-fit:cover;border-radius:6px;border:0.5px solid var(--ivica);flex-shrink:0;">
<form method="POST" action="/podesavanja/app-pozadina/ukloni">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
style="padding:7px 14px;background:transparent;border:0.5px solid #fca5a5;border-radius:8px;color:#dc2626;font-size:13px;cursor:pointer;"
data-potvrda="Ukloniti pozadinsku sliku aplikacije?">
Ukloni
</button>
</form>
</div>
{{end}}
<!-- upload forma -->
<form method="POST" action="/podesavanja/app-pozadina" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
<input type="file" id="app-bg-file" name="app_pozadina" accept=".jpg,.jpeg,.png,.webp"
style="display:none;">
<label for="app-bg-file"
style="display:inline-flex;align-items:center;padding:8px 14px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;color:var(--tekst-sporedni);cursor:pointer;user-select:none;">
Odaberi sliku
</label>
<button type="submit"
style="padding:8px 16px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;">
Otpremi sliku
</button>
</div>
<div style="font-size:12px;color:var(--tekst-sporedni);">JPG, PNG ili WebP — maksimum 5 MB</div>
</form>
<!-- pregled -->
<div x-data="{ blur: {{.AppPozadinaBlur}}, blurPozadine: {{.AppPozadinaBlurPozadine}}, opacity: {{.AppPozadinaOpacity}} }"
style="margin-top:20px;">
<!-- panel za pregled -->
<div style="position:relative;width:100%;height:200px;border-radius:8px;overflow:hidden;">
<!-- pozadinska slika sa blur-om -->
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .AppPozadina}}background:url('{{.AppPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div>
<!-- overlay: svi stilovi u :style jer Alpine cssText zamenjuje ceo style atribut -->
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div>
<!-- levi stub: simulacija sidebara sa glass stilom i inline SVG ikonama -->
<div :style="`position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px`">
<div style="width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.25);flex-shrink:0;"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M3 7h18M5 7l-2 14h18L19 7M9 7V5a3 3 0 016 0v2M9 21v-4h6v4"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
</div>
<!-- desna oblast -->
<div style="position:absolute;top:12px;left:68px;right:12px;z-index:2;display:flex;flex-direction:column;gap:7px;">
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Artikli</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">1.284</div>
</div>
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Servis</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">47</div>
</div>
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Prihodi</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">284.500</div>
</div>
</div>
</div>
<!-- slider: zamućenje pozadine -->
<div style="margin-top:16px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zamućenje pozadine</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="blurPozadine + 'px'"></span>
</div>
<input type="range" x-model.number="blurPozadine" min="0" max="20" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- slider: zamućenje stakla -->
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zamućenje stakla</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="blur + 'px'"></span>
</div>
<input type="range" x-model.number="blur" min="0" max="20" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- slider: zatamnjivanje -->
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="opacity + '%'"></span>
</div>
<input type="range" x-model.number="opacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- forma za čuvanje stilova -->
<form method="POST" action="/podesavanja/app-pozadina/stilovi" style="margin-top:16px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="blur" :value="blur">
<input type="hidden" name="blur_pozadine" :value="blurPozadine">
<input type="hidden" name="opacity" :value="opacity">
<button type="submit"
style="padding:9px 22px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj izgled
</button>
</form>
</div><!-- /x-data -->
</div>
<!-- pozadinska slika login stranice -->
<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);">
@@ -363,27 +233,21 @@
</form>
<!-- pregled login pozadine -->
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}} }"
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}}, zatamnjenjeKartice: {{.LoginPozadinaZatamnjenjeKartice}} }"
style="margin-top:20px;">
<!-- panel za pregled -->
<div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;">
<!-- pozadinska slika sa blur-om — mora biti zasebni div jer Alpine :style zamenjuje ceo style atribut -->
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .LoginPozadina}}background:url('{{.LoginPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div>
<!-- overlay zatamnjivanje -->
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div>
<!-- lažni login form u centru -->
<div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<div :style="`width:140px;border-radius:10px;background:rgba(255,255,255,0.08);backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`">
<div :style="`width:140px;border-radius:10px;background:rgba(0,0,0,${zatamnjenjeKartice/100});backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`">
<div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div>
<div style="height:22px;background:#e53e3e;border-radius:5px;opacity:0.85;"></div>
</div>
</div>
</div>
<!-- slider: zamućenje pozadine -->
@@ -406,22 +270,33 @@
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- slider: zatamnjivanje -->
<!-- slider: zatamnjivanje pozadine -->
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje</span>
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje pozadine</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="opacity + '%'"></span>
</div>
<input type="range" x-model.number="opacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- slider: zatamnjivanje kartice NTech -->
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje kartice NTech</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="zatamnjenjeKartice + '%'"></span>
</div>
<input type="range" x-model.number="zatamnjenjeKartice" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<!-- forma za čuvanje stilova -->
<form method="POST" action="/podesavanja/login-pozadina/stilovi" style="margin-top:16px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="blur_pozadine" :value="blurPozadine">
<input type="hidden" name="blur_kartice" :value="blurKartice">
<input type="hidden" name="opacity" :value="opacity">
<input type="hidden" name="zatamnjenje_kartice" :value="zatamnjenjeKartice">
<button type="submit"
style="padding:9px 22px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj izgled
@@ -431,40 +306,6 @@
</div><!-- /x-data -->
</div>
<!-- tema -->
<form method="POST" action="/podesavanja/sacuvaj">
<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"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Tema</span>
</div>
<div>
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:8px;">Izaberi temu</label>
<div class="teme-grid" style="display:flex;gap:10px;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .GlobalnaTema "tamna"}}border-color:var(--sb-akcent);{{end}}">
<input type="radio" name="globalna_tema" value="tamna" {{if eq .GlobalnaTema "tamna"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<div style="width:16px;height:16px;border-radius:50%;background:#1a1d27;flex-shrink:0;"></div>
<span style="font-size:13px;color:var(--tekst-glavni);">Tamna</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .GlobalnaTema "svetla"}}border-color:var(--sb-akcent);{{end}}">
<input type="radio" name="globalna_tema" value="svetla" {{if eq .GlobalnaTema "svetla"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<div style="width:16px;height:16px;border-radius:50%;background:#f0f2f5;border:0.5px solid #e2e6ed;flex-shrink:0;"></div>
<span style="font-size:13px;color:var(--tekst-glavni);">Svetla</span>
</label>
</div>
</div>
<div style="display:flex;justify-content:flex-end;margin-top:20px;">
<button type="submit"
style="background:var(--sb-akcent);color:#fff;border:none;padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:opacity 0.2s;"
onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">
Sačuvaj izgled
</button>
</div>
</div>
</form>
</div><!-- /izgled -->
<!-- ══════════════════════════════════════════════ SISTEM ══ -->
+12 -156
View File
@@ -27,115 +27,6 @@
<div class="poruka-uspeh">Podešavanja su uspešno sačuvana.</div>
{{end}}
<!-- pozadinska slika aplikacije -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
Pozadinska slika aplikacije
</div>
{{if .AppPozadina}}
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
<img src="{{.AppPozadina}}" alt="Trenutna pozadina"
style="width:120px;height:70px;object-fit:cover;border-radius:6px;border:0.5px solid var(--ivica);flex-shrink:0;">
<form method="POST" action="/podesavanja/app-pozadina/ukloni">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<button type="submit"
style="padding:7px 14px;background:transparent;border:0.5px solid #fca5a5;border-radius:8px;color:#dc2626;font-size:13px;cursor:pointer;"
data-potvrda="Ukloniti pozadinsku sliku aplikacije?">
Ukloni
</button>
</form>
</div>
{{end}}
<form method="POST" action="/podesavanja/app-pozadina" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
<input type="file" id="app-bg-file" name="app_pozadina" accept=".jpg,.jpeg,.png,.webp"
style="display:none;">
<label for="app-bg-file"
style="display:inline-flex;align-items:center;padding:8px 14px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;font-size:13px;color:var(--tekst-sporedni);cursor:pointer;user-select:none;">
Odaberi sliku
</label>
<button type="submit"
style="padding:8px 16px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:13px;font-weight:500;cursor:pointer;">
Otpremi sliku
</button>
</div>
<div style="font-size:12px;color:var(--tekst-sporedni);">JPG, PNG ili WebP — maksimum 5 MB</div>
</form>
<div x-data="{ blur: {{.AppPozadinaBlur}}, blurPozadine: {{.AppPozadinaBlurPozadine}}, opacity: {{.AppPozadinaOpacity}} }"
style="margin-top:20px;">
<div style="position:relative;width:100%;height:200px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .AppPozadina}}background:url('{{.AppPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div>
<div :style="`position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px`">
<div style="width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.25);flex-shrink:0;"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M3 7h18M5 7l-2 14h18L19 7M9 7V5a3 3 0 016 0v2M9 21v-4h6v4"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 002 1.61h9.72a2 2 0 002-1.61L23 6H6"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
</div>
<div style="position:absolute;top:12px;left:68px;right:12px;z-index:2;display:flex;flex-direction:column;gap:7px;">
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Artikli</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">1.284</div>
</div>
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Servis</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">47</div>
</div>
<div :style="`height:40px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Prihodi</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">284.500</div>
</div>
</div>
</div>
<div style="margin-top:16px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zamućenje pozadine</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="blurPozadine + 'px'"></span>
</div>
<input type="range" x-model.number="blurPozadine" min="0" max="20" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zamućenje stakla</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="blur + 'px'"></span>
</div>
<input type="range" x-model.number="blur" min="0" max="20" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="opacity + '%'"></span>
</div>
<input type="range" x-model.number="opacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<form method="POST" action="/podesavanja/app-pozadina/stilovi" style="margin-top:16px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="blur" :value="blur">
<input type="hidden" name="blur_pozadine" :value="blurPozadine">
<input type="hidden" name="opacity" :value="opacity">
<button type="submit"
style="padding:9px 22px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj izgled
</button>
</form>
</div><!-- /x-data -->
</div>
{{if index .Dozvole "podesavanja.login_pozadina"}}
<!-- pozadinska slika login stranice -->
<div class="kartica animiraj" style="margin-bottom:16px;">
@@ -171,14 +62,14 @@
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:6px;">JPG, PNG ili WebP — maksimum 5 MB</div>
</form>
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}} }"
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}}, zatamnjenjeKartice: {{.LoginPozadinaZatamnjenjeKartice}} }"
style="margin-top:20px;">
<div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .LoginPozadina}}background:url('{{.LoginPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div>
<div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<div :style="`width:140px;border-radius:10px;background:rgba(255,255,255,0.08);backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`">
<div :style="`width:140px;border-radius:10px;background:rgba(0,0,0,${zatamnjenjeKartice/100});backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`">
<div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div>
@@ -205,17 +96,26 @@
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje</span>
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje pozadine</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="opacity + '%'"></span>
</div>
<input type="range" x-model.number="opacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje kartice NTech</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="zatamnjenjeKartice + '%'"></span>
</div>
<input type="range" x-model.number="zatamnjenjeKartice" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<form method="POST" action="/podesavanja/login-pozadina/stilovi" style="margin-top:16px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="blur_pozadine" :value="blurPozadine">
<input type="hidden" name="blur_kartice" :value="blurKartice">
<input type="hidden" name="opacity" :value="opacity">
<input type="hidden" name="zatamnjenje_kartice" :value="zatamnjenjeKartice">
<button type="submit"
style="padding:9px 22px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj izgled
@@ -226,50 +126,6 @@
</div>
{{end}}
{{if index .Dozvole "tema.globalno"}}
<!-- globalna tema -->
<form method="POST" action="/podesavanja/sacuvaj">
<input type="hidden" name="_next" value="/admin/podesavanja/izgled">
<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"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Globalna tema</span>
</div>
<div style="font-size:13px;color:var(--tekst-sporedni);margin-bottom:12px;">Primenuje se na sve korisnike koji ne koriste svoju lokalnu temu.</div>
<div>
<div class="teme-grid" style="display:flex;gap:10px;">
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .GlobalnaTema "tamna"}}border-color:var(--sb-akcent);{{end}}">
<input type="radio" name="globalna_tema" value="tamna" {{if eq .GlobalnaTema "tamna"}}checked{{end}} style="accent-color:var(--sb-akcent);">
<div style="width:16px;height:16px;border-radius:50%;background:#1a1d27;flex-shrink:0;"></div>
<span style="font-size:13px;color:var(--tekst-glavni);">Tamna</span>
</label>
<label style="display:flex;align-items:center;gap:8px;padding:10px 14px;border:0.5px solid var(--ivica);border-radius:8px;cursor:pointer;flex:1;{{if eq .GlobalnaTema "svetla"}}border-color:var(--sb-akcent);{{end}}">
<input type="radio" name="globalna_tema" value="svetla"
{{if eq .GlobalnaTema "svetla"}}checked{{end}}
style="accent-color:var(--sb-akcent);">
<div style="width:16px;height:16px;border-radius:50%;background:#f0f2f5;border:0.5px solid #e2e6ed;flex-shrink:0;"></div>
<span style="font-size:13px;color:var(--tekst-glavni);">Svetla</span>
</label>
</div>
</div>
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:20px;">
<button type="submit" formaction="/podesavanja/tema/globalno"
style="background:transparent;color:var(--sb-akcent);border:0.5px solid var(--sb-akcent);padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:opacity 0.2s;"
onmouseover="this.style.opacity='0.75'" onmouseout="this.style.opacity='1'"
title="Primeni ovu temu na sve korisnike, poništi njihove lokalne teme">
Primeni globalno
</button>
<button type="submit"
style="background:var(--sb-akcent);color:#fff;border:none;padding:10px 24px;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:opacity 0.2s;"
onmouseover="this.style.opacity='0.85'" onmouseout="this.style.opacity='1'">
Sačuvaj
</button>
</div>
</div>
</form>
{{end}}
</div>
{{end}}
+1 -1
View File
@@ -26,7 +26,7 @@
}
{{if .LoginPozadina}}
.kartica {
background: rgba(255,255,255,0.08) !important;
background: rgba(0,0,0,{{.LoginPozadinaZatamnjenjeKartice}}%) !important;
backdrop-filter: blur({{.LoginPozadinaBlurKartice}}px);
-webkit-backdrop-filter: blur({{.LoginPozadinaBlurKartice}}px);
border: 1px solid rgba(255,255,255,0.18) !important;
+47 -35
View File
@@ -15,48 +15,52 @@
<!-- kartica: moja tema -->
<div class="kartica animiraj" x-data="{
globalnu: {{if .KoristiGlobalnuTemu}}true{{else}}false{{end}},
tema: '{{if .LokalnaTema}}{{.LokalnaTema}}{{else}}tamna{{end}}'
}" style="margin-bottom:16px;">
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
Moja tema
</div>
{{if not .AppPozadina}}
<div style="display:flex;align-items:center;justify-content:space-between;opacity:0.5;cursor:not-allowed;"
title="Administrator nije postavio pozadinsku sliku">
{{if .LokalnaPozadina}}
<!-- lična pozadina je aktivna — tamna tema se forsira automatski -->
<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(99,102,241,0.10);border:0.5px solid rgba(99,102,241,0.3);border-radius:8px;margin-bottom:14px;">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<div>
<div style="font-size:14px;color:var(--tekst-glavni);font-weight:500;">Koristi globalnu temu</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">Nema aktivne pozadinske slike</div>
<div style="font-size:13px;color:var(--tekst-glavni);font-weight:500;">Tamna tema je aktivna</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">Lična pozadinska slika automatski primenjuje tamnu temu.</div>
</div>
<label style="position:relative;display:inline-block;width:44px;height:24px;pointer-events:none;">
<span style="position:absolute;inset:0;border-radius:24px;background:var(--ivica);"></span>
<span style="position:absolute;left:2px;top:2px;width:20px;height:20px;background:#fff;border-radius:50%;"></span>
</label>
</div>
{{else}}
<form method="POST" action="/profil/tema">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div style="display:flex;flex-direction:column;gap:14px;">
<!-- svič: globalnu=true → koristi globalnu; globalnu=false → koristi lokalnu -->
<div style="display:flex;align-items:center;justify-content:space-between;">
<div>
<div style="font-size:14px;color:var(--tekst-glavni);font-weight:500;">Koristi globalnu temu</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;" x-text="globalnu ? 'Primenjuje se globalna tema sistema' : 'Primenjuje se vaša lična tema'"></div>
</div>
<label style="position:relative;display:inline-block;width:44px;height:24px;cursor:pointer;">
<input type="checkbox" style="opacity:0;width:0;height:0;position:absolute;"
x-model="globalnu">
<span :style="'position:absolute;inset:0;border-radius:24px;transition:0.2s;background:' + (globalnu ? 'var(--sb-akcent)' : 'var(--ivica)')"></span>
<span :style="'position:absolute;left:' + (globalnu ? '22px' : '2px') + ';top:2px;width:20px;height:20px;background:#fff;border-radius:50%;transition:0.2s;'"></span>
<!-- kada postoji lična pozadina, uvek čuvamo lokalni režim kako bi tema bila primenjena po uklanjanju pozadine -->
<input type="hidden" name="koristi_lokalnu_temu" value="1">
<div style="padding:12px;background:var(--pozadina);border-radius:8px;border:0.5px solid var(--ivica);">
<div style="font-size:13px;color:var(--tekst-sporedni);margin-bottom:10px;">Tema po uklanjanju pozadine:</div>
<div style="display:flex;gap:16px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--tekst-glavni);">
<input type="radio" name="lokalna_tema" value="tamna" x-model="tema">
Tamna
</label>
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--tekst-glavni);">
<input type="radio" name="lokalna_tema" value="svetla" x-model="tema">
Svetla
</label>
<!-- globalnu=true → koristi_lokalnu_temu=0; globalnu=false → koristi_lokalnu_temu=1 -->
<input type="hidden" name="koristi_lokalnu_temu" :value="globalnu ? '0' : '1'">
</div>
<!-- radio dugmići vidljivi samo kada je lokalna tema aktivna -->
<div x-show="!globalnu" x-transition style="padding:12px;background:var(--pozadina);border-radius:8px;border:0.5px solid var(--ivica);">
</div>
<div style="margin-top:12px;">
<button type="submit"
style="padding:9px 20px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj
</button>
</div>
</form>
{{else}}
<!-- nema lične pozadine — direktan izbor između tamne i svetle teme -->
<form method="POST" action="/profil/tema">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="koristi_lokalnu_temu" value="1">
<div style="display:flex;flex-direction:column;gap:14px;">
<div style="padding:12px;background:var(--pozadina);border-radius:8px;border:0.5px solid var(--ivica);">
<div style="font-size:13px;color:var(--tekst-sporedni);margin-bottom:10px;">Izaberite temu:</div>
<div style="display:flex;gap:16px;">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--tekst-glavni);">
@@ -69,7 +73,6 @@
</label>
</div>
</div>
<div>
<button type="submit"
style="padding:9px 20px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
@@ -120,24 +123,24 @@
<div style="font-size:12px;color:var(--tekst-sporedni);">JPG, PNG ili WebP — maksimum 5 MB</div>
</form>
<div x-data="{ blur: {{.LokalnaPozadinaBlur}}, opacity: {{.LokalnaPozadinaOpacity}}, blurPozadine: {{.LokalnaPozadinaBlurPozadine}} }"
<div x-data="{ blur: {{.LokalnaPozadinaBlur}}, opacity: {{.LokalnaPozadinaOpacity}}, blurPozadine: {{.LokalnaPozadinaBlurPozadine}}, glassOpacity: {{.LokalnaPozadinaGlassOpacity}} }"
style="margin-top:20px;">
<div style="position:relative;width:100%;height:180px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:0;{{if .LokalnaPozadina}}background:url('{{.LokalnaPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}z-index:0;filter:blur(${blurPozadine}px);-webkit-filter:blur(${blurPozadine}px);transform:scale(1.05);`"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div>
<div :style="`position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px`">
<div :style="`position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px`">
<div style="width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.25);flex-shrink:0;"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
</div>
<div style="position:absolute;top:12px;left:68px;right:12px;z-index:2;display:flex;flex-direction:column;gap:7px;">
<div :style="`height:36px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div :style="`height:36px;border-radius:6px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Artikli</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">1.284</div>
</div>
<div :style="`height:36px;border-radius:6px;background:rgba(255,255,255,0.10);backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div :style="`height:36px;border-radius:6px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Servis</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">47</div>
</div>
@@ -162,17 +165,26 @@
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje</span>
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje pozadine</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="opacity + '%'"></span>
</div>
<input type="range" x-model.number="opacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<div style="margin-top:12px;">
<div style="display:flex;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:13px;color:var(--tekst-sporedni);">Zatamnjivanje stakla</span>
<span style="font-size:13px;color:var(--tekst-glavni);font-weight:500;" x-text="glassOpacity + '%'"></span>
</div>
<input type="range" x-model.number="glassOpacity" min="0" max="80" step="1"
style="width:100%;accent-color:var(--sb-akcent);cursor:pointer;">
</div>
<form method="POST" action="/profil/pozadina/stilovi" style="margin-top:16px;">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="lokalna_pozadina_opacity" :value="opacity">
<input type="hidden" name="lokalna_pozadina_blur" :value="blur">
<input type="hidden" name="lokalna_pozadina_blur_pozadine" :value="blurPozadine">
<input type="hidden" name="lokalna_pozadina_glass_opacity" :value="glassOpacity">
<button type="submit"
style="padding:9px 22px;background:var(--sb-akcent);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
Sačuvaj izgled
+24 -8
View File
@@ -12,6 +12,17 @@
if (window.innerWidth > 768 && localStorage.getItem('sidebar-skupljen') === 'true') {
document.documentElement.classList.add('sidebar-init-skupljen');
}
// pozadinska slika iz prethodne stranice — sprečava treperenje pri navigaciji
(function() {
var bg = sessionStorage.getItem('ntech-bg');
if (bg) {
var s = document.documentElement.style;
s.backgroundImage = 'url(' + bg + ')';
s.backgroundSize = 'cover';
s.backgroundPosition = 'center';
s.backgroundAttachment = 'fixed';
}
})();
</script>
<!-- tema — učitava se prva -->
@@ -36,11 +47,6 @@
filter: blur({{.AppPozadinaBlurPozadine}}px);
pointer-events: none;
z-index: 0;
animation: appBgFadeIn 0.35s ease forwards;
}
@keyframes appBgFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.app-overlay {
position: fixed;
@@ -54,7 +60,7 @@
z-index: 2;
}
.sidebar {
background: rgba(0,0,0,0.3) !important;
background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.3{{end}}) !important;
backdrop-filter: blur({{.AppPozadinaBlur}}px);
-webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px);
border-right: 1px solid rgba(255,255,255,0.12) !important;
@@ -74,13 +80,13 @@
color: rgba(255,255,255,0.7) !important;
}
.topbar {
background: rgba(255,255,255,0.08) !important;
background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important;
backdrop-filter: blur({{.AppPozadinaBlur}}px);
-webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px);
border-bottom: 1px solid rgba(255,255,255,0.12) !important;
}
.kartica {
background: rgba(255,255,255,0.08) !important;
background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important;
backdrop-filter: blur({{.AppPozadinaBlur}}px);
-webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px);
border: 1px solid rgba(255,255,255,0.12) !important;
@@ -216,6 +222,16 @@
{{block "dodatni-js" .}}{{end}}
<!-- čuva URL pozadine za sledeću navigaciju (bez treperenja) -->
<script>
{{if .AppPozadina}}
sessionStorage.setItem('ntech-bg', '{{.AppPozadina}}');
{{else}}
sessionStorage.removeItem('ntech-bg');
document.documentElement.style.backgroundImage = '';
{{end}}
</script>
<!-- CSRF: automatski dodaje skriveno polje u sve POST forme -->
<script>
document.addEventListener('DOMContentLoaded', function() {