From 8def13e855c769a1bd4a4586b5f2c1e5ab9be43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Fri, 5 Jun 2026 23:49:05 +0200 Subject: [PATCH] =?UTF-8?q?Pode=C5=A1avanja:=20pozadinska=20slika=20i=20gl?= =?UTF-8?q?ass=20efekt=20za=20aplikaciju=20sa=20live=20preview=20sliderom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/ntech/main.go | 5 + internal/handler/handler.go | 11 + internal/handler/podesavanja.go | 362 ++++++++++++++++++++- internal/handler/prijava.go | 13 +- internal/middleware/dozvole.go | 5 + internal/model/servis.go | 26 ++ internal/model/stranica.go | 4 + migrations/023_login_pozadina.sql | 1 + migrations/024_app_pozadina.sql | 3 + web/static/favicon.svg | 51 +++ web/templates/stranice/podesavanja.html | 145 +++++++++ web/templates/stranice/prijava.html | 24 ++ web/templates/stranice/servis_detalji.html | 8 + web/templates/teme/podrazumevana/base.html | 42 +++ 14 files changed, 685 insertions(+), 15 deletions(-) create mode 100644 migrations/023_login_pozadina.sql create mode 100644 migrations/024_app_pozadina.sql create mode 100644 web/static/favicon.svg diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index ec678eb..301ad58 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -142,6 +142,11 @@ func main() { r.Get("/podesavanja", h.Podesavanja) r.Post("/podesavanja/sacuvaj", h.SacuvajPodesavanja) r.Post("/podesavanja/logo", h.OtpremiLogo) + r.Post("/podesavanja/login-pozadina", h.OtpremiLoginPozadinu) + r.Post("/podesavanja/login-pozadina/ukloni", h.UkloniLoginPozadinu) + 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) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index bb33f60..2fda06a 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -90,5 +90,16 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s } ps.CsrfToken = middleware.CsrfToken(r.Context()) ps.Flash = middleware.GetFlash(r, h.DB) + + ps.AppPozadina = podesavanja["app_pozadina"] + ps.AppPozadinaOpacity = podesavanja["app_pozadina_opacity"] + if ps.AppPozadinaOpacity == "" { + ps.AppPozadinaOpacity = "50" + } + ps.AppPozadinaBlur = podesavanja["app_pozadina_blur"] + if ps.AppPozadinaBlur == "" { + ps.AppPozadinaBlur = "12" + } + return ps } diff --git a/internal/handler/podesavanja.go b/internal/handler/podesavanja.go index 89f9016..b555eda 100644 --- a/internal/handler/podesavanja.go +++ b/internal/handler/podesavanja.go @@ -1,6 +1,8 @@ package handler import ( + "crypto/rand" + "encoding/hex" "fmt" "io" "log" @@ -9,6 +11,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "time" @@ -22,19 +25,23 @@ import ( // PodaciPodesavanja su podaci za stranicu podešavanja type PodaciPodesavanja struct { model.PodaciStranice - NazivFirme string - Podnazlov string - Adresa string - Telefon string - PIB string - LogoTip string - LogoPutanja string - Tema string - Sacuvano bool - Verzija string - LogoGreska string - BackupVracen bool - Backupi []BackupInfo + NazivFirme string + Podnazlov string + Adresa string + Telefon string + PIB string + LogoTip string + LogoPutanja string + Tema string + Sacuvano bool + Verzija string + LogoGreska string + BackupVracen bool + Backupi []BackupInfo + LoginPozadina string + AppPozadina string + AppPozadinaOpacity string + AppPozadinaBlur string } // BackupInfo opisuje jedan backup fajl @@ -78,6 +85,18 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) { Verzija: h.Verzija, LogoGreska: r.URL.Query().Get("logo_greska"), Backupi: ucitajListuBackupa(), + LoginPozadina: podesavanja["login_pozadina"], + 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 + }(), } h.renderujTemplate(w, "podesavanja", podaci) @@ -341,6 +360,323 @@ func (h *Handler) OtpremiLogo(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/podesavanja?sacuvano=1", http.StatusSeeOther) } +// generisiImeUploada vraća slučajno hex ime (16 bajtova) sa datom ekstenzijom +func generisiImeUploada(ext string) (string, error) { + buf := make([]byte, 16) + if _, err := rand.Read(buf); err != nil { + return "", err + } + return hex.EncodeToString(buf) + ext, nil +} + +// OtpremiLoginPozadinu prima multipart upload slike i čuva je kao pozadinsku sliku login stranice +func (h *Handler) OtpremiLoginPozadinu(w http.ResponseWriter, r *http.Request) { + k := middleware.KorisnikIzKonteksta(r.Context()) + if k == nil || !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "podesavanja.login_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, "/podesavanja", http.StatusSeeOther) + return + } + + fajl, zaglavlje, err := r.FormFile("login_pozadina") + if err != nil { + middleware.SetFlash(w, r, h.DB, "greska", "Nije odabran fajl.") + http.Redirect(w, r, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + // proveravamo stvarni tip sadržaja (magic bytes) + 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, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + // briše staru pozadinu sa diska ako postoji + staraPodesavanja, _ := ntechsqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + if stara := staraPodesavanja["login_pozadina"]; stara != "" { + // putanja u bazi je oblika /static/uploads/ime.ext?v=..., izvlačimo samo ime fajla + 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 login pozadine: greška pri generisanju imena: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + + odrediste := filepath.Join("web/static/uploads", novoIme) + dst, err := os.Create(odrediste) + if err != nil { + log.Printf("upload login pozadine: ne mogu kreirati fajl: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + defer dst.Close() + + if _, err := io.Copy(dst, fajl); err != nil { + log.Printf("upload login pozadine: greška pri kopiranju: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju fajla.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + + putanja := fmt.Sprintf("/static/uploads/%s?v=%d", novoIme, time.Now().Unix()) + if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "login_pozadina", putanja); err != nil { + log.Printf("upload login 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, "/podesavanja", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika je uspešno otpremljena.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) +} + +// UkloniLoginPozadinu briše pozadinsku sliku login stranice sa diska i iz podešavanja +func (h *Handler) UkloniLoginPozadinu(w http.ResponseWriter, r *http.Request) { + k := middleware.KorisnikIzKonteksta(r.Context()) + if k == nil || !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "podesavanja.login_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["login_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, "login_pozadina", ""); err != nil { + log.Printf("ukloni login pozadinu: greška pri čuvanju: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju slike.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika je uklonjena.") + http.Redirect(w, r, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika aplikacije je uspešno otpremljena.") + http.Redirect(w, r, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika aplikacije je uklonjena.") + http.Redirect(w, r, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + blurStr := r.FormValue("blur") + 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.") + http.Redirect(w, r, "/podesavanja", 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, "/podesavanja", http.StatusSeeOther) + return + } + + if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "app_pozadina_blur", blurStr); err != nil { + log.Printf("stilovi app pozadine: greška pri čuvanju blur: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "app_pozadina_opacity", opacityStr); err != nil { + log.Printf("stilovi app pozadine: greška pri čuvanju opacity: %v", err) + middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju podešavanja.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) + return + } + + middleware.SetFlash(w, r, h.DB, "uspeh", "Izgled pozadine aplikacije je sačuvan.") + http.Redirect(w, r, "/podesavanja", http.StatusSeeOther) +} + // PromeniTemu menja aktivnu temu i vraća na prethodnu stranicu func (h *Handler) PromeniTemu(w http.ResponseWriter, r *http.Request) { tema := chi.URLParam(r, "tema") diff --git a/internal/handler/prijava.go b/internal/handler/prijava.go index 0d29e2d..e842306 100644 --- a/internal/handler/prijava.go +++ b/internal/handler/prijava.go @@ -9,6 +9,7 @@ import ( "time" "ntech/internal/auth" + ntechsqlite "ntech/internal/db/sqlite" "ntech/internal/middleware" ) @@ -29,9 +30,17 @@ func (h *Handler) PrikazPrijave(w http.ResponseWriter, r *http.Request) { } greska := r.URL.Query().Get("greska") + + // login_pozadina se čita bez prijavljenog korisnika — koristimo background kontekst + loginPozadina := "" + if podesavanja, err := ntechsqlite.DohvatiSvaPodesavanja(context.Background(), h.DB); err == nil { + loginPozadina = podesavanja["login_pozadina"] + } + h.renderujStandalone(w, "prijava", map[string]any{ - "Greska": greska, - "CsrfToken": middleware.CsrfToken(r.Context()), + "Greska": greska, + "CsrfToken": middleware.CsrfToken(r.Context()), + "LoginPozadina": loginPozadina, }) } diff --git a/internal/middleware/dozvole.go b/internal/middleware/dozvole.go index 6496253..a672e2d 100644 --- a/internal/middleware/dozvole.go +++ b/internal/middleware/dozvole.go @@ -43,6 +43,8 @@ var sveAkcije = []string{ "korisnik.uloga", "backup.pregled", "backup.pokreni", + "podesavanja.login_pozadina", + "podesavanja.app_pozadina", } // SveAkcije vraća listu svih poznatih akcija — koristi se pri inicijalizaciji baze i resetu @@ -100,6 +102,9 @@ func ImaDozvolu(uloga, akcija string) bool { // backup case "backup.pregled", "backup.pokreni": return true + // pozadinske slike + case "podesavanja.login_pozadina", "podesavanja.app_pozadina": + return true } return false diff --git a/internal/model/servis.go b/internal/model/servis.go index 49b676d..07ec704 100644 --- a/internal/model/servis.go +++ b/internal/model/servis.go @@ -88,3 +88,29 @@ func (n ServisniNalog) AvansStr() string { } return fmt.Sprintf("%.2f", *n.Avans) } + +// PreostaloZaNaplatu vraća razliku konacna_cena − avans, minimum 0. +// Vraća nil ako konačna cena nije uneta. +func (n ServisniNalog) PreostaloZaNaplatu() *float64 { + if n.CenaKonacna == nil { + return nil + } + avans := 0.0 + if n.Avans != nil { + avans = *n.Avans + } + v := *n.CenaKonacna - avans + if v < 0 { + v = 0 + } + return &v +} + +// PreostaloZaNaplatuStr vraća formatirano preostalo za naplatu, ili prazan string +func (n ServisniNalog) PreostaloZaNaplatuStr() string { + v := n.PreostaloZaNaplatu() + if v == nil { + return "" + } + return fmt.Sprintf("%.2f", *v) +} diff --git a/internal/model/stranica.go b/internal/model/stranica.go index 0be0795..658a191 100644 --- a/internal/model/stranica.go +++ b/internal/model/stranica.go @@ -44,6 +44,10 @@ type PodaciStranice struct { CsrfToken string // CSRF zaštitni token za forme Dozvole map[string]bool // mapa akcija → dozvoljeno/nije Flash *FlashPoruka // jednokratna poruka nakon redirecta + // app pozadina — popunjava se iz podešavanja za sve stranice + AppPozadina string + AppPozadinaOpacity string // vrednost 0-80 (% overlay zatamnjivanja) + AppPozadinaBlur string // vrednost 0-20 (px backdrop-filter blur) } // PodaciDashboarda su podaci specifični za dashboard stranicu diff --git a/migrations/023_login_pozadina.sql b/migrations/023_login_pozadina.sql new file mode 100644 index 0000000..f3576fa --- /dev/null +++ b/migrations/023_login_pozadina.sql @@ -0,0 +1 @@ +INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('login_pozadina', ''); diff --git a/migrations/024_app_pozadina.sql b/migrations/024_app_pozadina.sql new file mode 100644 index 0000000..72fbc55 --- /dev/null +++ b/migrations/024_app_pozadina.sql @@ -0,0 +1,3 @@ +INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('app_pozadina', ''); +INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('app_pozadina_opacity', '50'); +INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('app_pozadina_blur', '12'); diff --git a/web/static/favicon.svg b/web/static/favicon.svg new file mode 100644 index 0000000..1bc68f7 --- /dev/null +++ b/web/static/favicon.svg @@ -0,0 +1,51 @@ + +NTech favicon v4 +Moderna ikonica sa NT slovima i tehnološkim akcentom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/templates/stranice/podesavanja.html b/web/templates/stranice/podesavanja.html index 44520f6..033b194 100644 --- a/web/templates/stranice/podesavanja.html +++ b/web/templates/stranice/podesavanja.html @@ -85,6 +85,151 @@ + +
+ +
+ + Pozadinska slika aplikacije +
+ + {{if .AppPozadina}} +
+ Trenutna app pozadina +
+ +
+
+ {{end}} + +
+
+ + +
+
JPG, PNG ili WebP — maksimum 5 MB
+
+ + +
+
Preview
+ + +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+
+ + +
+
+
+ Zamućenje stakla + +
+ +
+
+
+ Zatamnjivanje + +
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+
+ + Pozadinska slika prijave +
+ {{if .LoginPozadina}} +
+ Trenutna pozadina + + + +
+ {{end}} +
+ + +
+
JPG, PNG ili WebP — maksimum 5 MB
+
+ +
diff --git a/web/templates/stranice/prijava.html b/web/templates/stranice/prijava.html index 2063940..4eb0e2a 100644 --- a/web/templates/stranice/prijava.html +++ b/web/templates/stranice/prijava.html @@ -14,7 +14,31 @@ align-items: center; justify-content: center; padding: 16px; + position: relative; } + {{if .LoginPozadina}} + body::before { + content: ''; + position: fixed; + inset: 0; + background-image: url('{{.LoginPozadina}}'); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: 0; + } + body::after { + content: ''; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.40); + z-index: 1; + } + .kartica { + position: relative; + z-index: 2; + } + {{end}} .kartica { background: #1a1d27; border: 0.5px solid #2d3148; diff --git a/web/templates/stranice/servis_detalji.html b/web/templates/stranice/servis_detalji.html index af009c6..8ac3686 100644 --- a/web/templates/stranice/servis_detalji.html +++ b/web/templates/stranice/servis_detalji.html @@ -148,6 +148,14 @@ {{if .Nalog.Avans}}{{.Nalog.AvansStr}} din{{else}}—{{end}} + {{if .Nalog.PreostaloZaNaplatu}} +
+
Preostalo za naplatu
+
+ {{.Nalog.PreostaloZaNaplatuStr}} din +
+
+ {{end}} diff --git a/web/templates/teme/podrazumevana/base.html b/web/templates/teme/podrazumevana/base.html index 6f2f196..3cb5fdd 100644 --- a/web/templates/teme/podrazumevana/base.html +++ b/web/templates/teme/podrazumevana/base.html @@ -2,6 +2,7 @@ + {{block "naslov" .}}NTech{{end}} @@ -23,8 +24,49 @@ {{block "dodatni-css" .}}{{end}} + + {{if .AppPozadina}} + + {{end}} + {{if .AppPozadina}}
{{end}}
{{template "sidebar" .}}