Bezbednosni audit i refaktoring: HP popravke, RBAC, flash poruke, go:embed, CSP
This commit is contained in:
+1
-1
@@ -234,7 +234,7 @@ func main() {
|
||||
r.Get("/admin/profil/totp/pokreni", h.AdminTotpPokreni)
|
||||
r.Post("/admin/profil/totp/aktiviraj", h.AdminTotpAktivacija)
|
||||
r.Post("/admin/profil/totp/deaktiviraj", h.AdminTotpDeaktivacija)
|
||||
r.Post("/profil/tema", h.AdminSacuvajLokalnuTemu)
|
||||
r.Post("/profil/tema", h.SacuvajLokalnuTemu)
|
||||
r.Get("/profil/tema", h.ProfilTema)
|
||||
r.Post("/profil/pozadina", h.ProfilOtpremiPozadinu)
|
||||
r.Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu)
|
||||
|
||||
@@ -11,6 +11,33 @@ import (
|
||||
|
||||
type sqliteKorisniciRepo struct{ db *sql.DB }
|
||||
|
||||
// skeniraiKorisnika čita jedan red iz baze i popunjava model.Korisnik
|
||||
func skeniraiKorisnika(row interface{ Scan(...any) error }) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
if err := row.Scan(
|
||||
&k.ID, &k.KorisnickoIme, &k.LozinkaHash, &k.Uloga, &aktivan, &k.TotpTajna,
|
||||
&lokalnaTema, &koristiLokalnuTemu, &datumKreiranja,
|
||||
&lokalnaPozadina, &lokalnaPozadinaOpacity, &lokalnaPozadinaBlur,
|
||||
&lokalnaPozadinaBlurPozadine, &lokalnaPozadinaGlassOpacity,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// NoviKorisniciRepo kreira SQLite implementaciju KorisniciRepository
|
||||
func NoviKorisniciRepo(db *sql.DB) *sqliteKorisniciRepo {
|
||||
return &sqliteKorisniciRepo{db: db}
|
||||
@@ -28,67 +55,32 @@ func (r *sqliteKorisniciRepo) Kreiraj(ctx context.Context, korisnickoIme, lozink
|
||||
}
|
||||
|
||||
func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme string) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var totpTajna, lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
row := 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_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,
|
||||
&lokalnaPozadinaGlassOpacity)
|
||||
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme)
|
||||
k, err := skeniraiKorisnika(row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoImenu: %w", err)
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.TotpTajna = totpTajna.String
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model.Korisnik, error) {
|
||||
k := &model.Korisnik{}
|
||||
var aktivan, koristiLokalnuTemu int
|
||||
var lokalnaTema sql.NullString
|
||||
var lokalnaPozadina, lokalnaPozadinaOpacity, lokalnaPozadinaBlur, lokalnaPozadinaBlurPozadine, lokalnaPozadinaGlassOpacity sql.NullString
|
||||
var datumKreiranja time.Time
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
row := 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_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,
|
||||
&lokalnaPozadinaGlassOpacity)
|
||||
FROM korisnici WHERE id = ?`, id)
|
||||
k, err := skeniraiKorisnika(row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ntech: korisnici.DohvatiPoID: %w", err)
|
||||
}
|
||||
k.Aktivan = aktivan == 1
|
||||
k.LokalnaTema = lokalnaTema.String
|
||||
k.KoristiLokalnuTemu = koristiLokalnuTemu == 1
|
||||
k.DatumKreiranja = datumKreiranja
|
||||
k.LokalnaPozadina = lokalnaPozadina.String
|
||||
k.LokalnaPozadinaOpacity = lokalnaPozadinaOpacity.String
|
||||
k.LokalnaPozadinaBlur = lokalnaPozadinaBlur.String
|
||||
k.LokalnaPozadinaBlurPozadine = lokalnaPozadinaBlurPozadine.String
|
||||
k.LokalnaPozadinaGlassOpacity = lokalnaPozadinaGlassOpacity.String
|
||||
return k, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -305,44 +305,6 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// AdminSacuvajLokalnuTemu čuva korisnikovu lokalnu temu
|
||||
func (h *Handler) AdminSacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
if k == nil {
|
||||
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") {
|
||||
http.Error(w, "Pristup odbijen", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
koristi := r.FormValue("koristi_lokalnu_temu") == "1"
|
||||
lokalnaTema := r.FormValue("lokalna_tema")
|
||||
if lokalnaTema != "tamna" && lokalnaTema != "svetla" {
|
||||
lokalnaTema = "tamna"
|
||||
}
|
||||
|
||||
if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.")
|
||||
// vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback
|
||||
if ref := r.Referer(); ref != "" {
|
||||
http.Redirect(w, r, ref, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// AdminPromeniLozinku menja lozinku prijavljenog korisnika
|
||||
func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -70,7 +70,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
|
||||
if korisnikDash.Uloga == "radnik" {
|
||||
korisnikFilter.KorisnikID = &korisnikDash.ID
|
||||
}
|
||||
if n, err := h.PodsetniciFRepo.BrojAktivnih(ctx, korisnikFilter); err != nil {
|
||||
if n, err := h.PodsetnikRepo.BrojAktivnih(ctx, korisnikFilter); err != nil {
|
||||
log.Printf("dashboard: aktivni podsetnici: %v", err)
|
||||
} else {
|
||||
aktivniPodsetnici = n
|
||||
|
||||
@@ -25,7 +25,7 @@ type Handler struct {
|
||||
ProdajaRepo db.ProdajaRepository
|
||||
KorisniciRepo db.KorisniciRepository
|
||||
SesijeRepo db.SesijeRepository
|
||||
PodsetniciFRepo db.PodsetnikRepository
|
||||
PodsetnikRepo db.PodsetnikRepository
|
||||
PokusajiRepo db.PokusajiPrijaveRepository
|
||||
LoginIstorijsaRepo db.LoginIstorijsaRepository
|
||||
DozvoleRepo db.DozvoleRepository
|
||||
@@ -47,15 +47,15 @@ func Novi(baza *sql.DB) *Handler {
|
||||
ProdajaRepo: sqlite.NoviProdajaRepo(baza),
|
||||
KorisniciRepo: sqlite.NoviKorisniciRepo(baza),
|
||||
SesijeRepo: sqlite.NoviSesijeRepo(baza),
|
||||
PodsetniciFRepo: sqlite.NoviPodsetnikRepo(baza),
|
||||
PodsetnikRepo: sqlite.NoviPodsetnikRepo(baza),
|
||||
PokusajiRepo: sqlite.NoviPokusajiPrijaveRepo(baza),
|
||||
LoginIstorijsaRepo: sqlite.NoviLoginIstorijsaRepo(baza),
|
||||
DozvoleRepo: sqlite.NoviDozvoleRepo(baza, middleware.ImaDozvolu, middleware.SveAkcije()),
|
||||
}
|
||||
}
|
||||
|
||||
// reinicijalzijRepozitorijume zamenjuje sve repozitorijume posle obnove baze
|
||||
func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) {
|
||||
// reinicijalizujRepozitorijume zamenjuje sve repozitorijume posle obnove baze
|
||||
func (h *Handler) reinicijalizujRepozitorijume(novaDB *sql.DB) {
|
||||
h.DB = novaDB
|
||||
h.Artikli = sqlite.NoviArtikalRepo(novaDB)
|
||||
h.KategorijeRepo = sqlite.NovaKategorijaRepo(novaDB)
|
||||
@@ -66,7 +66,7 @@ func (h *Handler) reinicijalzijRepozitorijume(novaDB *sql.DB) {
|
||||
h.ProdajaRepo = sqlite.NoviProdajaRepo(novaDB)
|
||||
h.KorisniciRepo = sqlite.NoviKorisniciRepo(novaDB)
|
||||
h.SesijeRepo = sqlite.NoviSesijeRepo(novaDB)
|
||||
h.PodsetniciFRepo = sqlite.NoviPodsetnikRepo(novaDB)
|
||||
h.PodsetnikRepo = sqlite.NoviPodsetnikRepo(novaDB)
|
||||
h.PokusajiRepo = sqlite.NoviPokusajiPrijaveRepo(novaDB)
|
||||
h.LoginIstorijsaRepo = sqlite.NoviLoginIstorijsaRepo(novaDB)
|
||||
h.DozvoleRepo = sqlite.NoviDozvoleRepo(novaDB, middleware.ImaDozvolu, middleware.SveAkcije())
|
||||
|
||||
@@ -83,26 +83,10 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
|
||||
LogoGreska: r.URL.Query().Get("logo_greska"),
|
||||
Backupi: ucitajListuBackupa(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: func() string {
|
||||
v := podesavanja["login_pozadina_opacity"]
|
||||
if v == "" { return "50" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaBlurPozadine: func() string {
|
||||
v := podesavanja["login_pozadina_blur_pozadine"]
|
||||
if v == "" { return "0" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaBlurKartice: func() string {
|
||||
v := podesavanja["login_pozadina_blur_kartice"]
|
||||
if v == "" { return "12" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaZatamnjenjeKartice: func() string {
|
||||
v := podesavanja["login_pozadina_zatamnjenje_kartice"]
|
||||
if v == "" { return "0" }
|
||||
return v
|
||||
}(),
|
||||
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
|
||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||
}
|
||||
|
||||
h.renderujTemplate(w, "podesavanja", podaci)
|
||||
@@ -136,6 +120,14 @@ func ucitajListuBackupa() []BackupInfo {
|
||||
return lista
|
||||
}
|
||||
|
||||
// vrednostIliDefault vraća vrednost iz mape ako postoji i nije prazan string, inače vraća podrazumevanu vrednost
|
||||
func vrednostIliDefault(m map[string]string, kljuc, podrazumevano string) string {
|
||||
if v := m[kljuc]; v != "" {
|
||||
return v
|
||||
}
|
||||
return podrazumevano
|
||||
}
|
||||
|
||||
// VratiBackup zamenjuje trenutnu bazu sa izabranim backup fajlom
|
||||
func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
@@ -197,7 +189,7 @@ func (h *Handler) VratiBackup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
h.reinicijalzijRepozitorijume(novaDB)
|
||||
h.reinicijalizujRepozitorijume(novaDB)
|
||||
log.Printf("Baza uspešno obnovljena iz: %s", ime)
|
||||
|
||||
http.Redirect(w, r, "/podesavanja?sacuvano=vraceno", http.StatusSeeOther)
|
||||
@@ -595,30 +587,10 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
||||
LogoGreska: r.URL.Query().Get("logo_greska"),
|
||||
Backupi: ucitajListuBackupa(),
|
||||
LoginPozadina: podesavanja["login_pozadina"],
|
||||
LoginPozadinaOpacity: func() string {
|
||||
if v := podesavanja["login_pozadina_opacity"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "50"
|
||||
}(),
|
||||
LoginPozadinaBlurPozadine: func() string {
|
||||
if v := podesavanja["login_pozadina_blur_pozadine"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "0"
|
||||
}(),
|
||||
LoginPozadinaBlurKartice: func() string {
|
||||
if v := podesavanja["login_pozadina_blur_kartice"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "12"
|
||||
}(),
|
||||
LoginPozadinaZatamnjenjeKartice: func() string {
|
||||
if v := podesavanja["login_pozadina_zatamnjenje_kartice"]; v != "" {
|
||||
return v
|
||||
}
|
||||
return "0"
|
||||
}(),
|
||||
LoginPozadinaOpacity: vrednostIliDefault(podesavanja, "login_pozadina_opacity", "50"),
|
||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func (h *Handler) Podsetnici(w http.ResponseWriter, r *http.Request) {
|
||||
filter.KorisnikID = &k.ID
|
||||
}
|
||||
|
||||
lista, err := h.PodsetniciFRepo.Lista(r.Context(), filter)
|
||||
lista, err := h.PodsetnikRepo.Lista(r.Context(), filter)
|
||||
if err != nil {
|
||||
http.Error(w, "Greška pri učitavanju podsetnika", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -88,7 +88,7 @@ func (h *Handler) NoviPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
|
||||
@@ -110,32 +110,14 @@ func (h *Handler) SacuvajPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
podsetnik, greska := parseFormuPodsetnika(r, k)
|
||||
|
||||
prikaziGresku := func(poruka string) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: false,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
if greska != "" {
|
||||
prikaziGresku(greska)
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, false)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.PodsetniciFRepo.Kreiraj(r.Context(), &podsetnik); err != nil {
|
||||
if _, err := h.PodsetnikRepo.Kreiraj(r.Context(), &podsetnik); err != nil {
|
||||
log.Printf("greška pri čuvanju podsetnika: %v", err)
|
||||
prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.")
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,7 +134,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id)
|
||||
podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
@@ -169,7 +151,7 @@ func (h *Handler) IzmeniPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
|
||||
@@ -199,32 +181,14 @@ func (h *Handler) SacuvajIzmenePodsetnika(w http.ResponseWriter, r *http.Request
|
||||
podsetnik, greska := parseFormuPodsetnika(r, k)
|
||||
podsetnik.ID = id
|
||||
|
||||
prikaziGresku := func(poruka string) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
var korisnici []model.Korisnik
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: true,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
if greska != "" {
|
||||
prikaziGresku(greska)
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, greska, true)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.Izmeni(r.Context(), &podsetnik); err != nil {
|
||||
if err := h.PodsetnikRepo.Izmeni(r.Context(), &podsetnik); err != nil {
|
||||
log.Printf("greška pri čuvanju izmene podsetnika: %v", err)
|
||||
prikaziGresku("Došlo je do greške pri čuvanju. Pokušajte ponovo.")
|
||||
h.prikaziGreskuPodsetnika(w, r, k, podsetnik, "Došlo je do greške pri čuvanju. Pokušajte ponovo.", true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -240,13 +204,13 @@ func (h *Handler) OznaciPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// učitamo trenutni status pa ga preokrenemo
|
||||
podsetnik, err := h.PodsetniciFRepo.DohvatiID(r.Context(), id)
|
||||
podsetnik, err := h.PodsetnikRepo.DohvatiID(r.Context(), id)
|
||||
if err != nil {
|
||||
http.Error(w, "Podsetnik nije pronađen", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil {
|
||||
if err := h.PodsetnikRepo.OznaciZavrsenim(r.Context(), id, !podsetnik.Zavrseno); err != nil {
|
||||
http.Error(w, "Greška pri ažuriranju statusa", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -262,7 +226,7 @@ func (h *Handler) ObrisiPodsetnik(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.PodsetniciFRepo.Obrisi(r.Context(), id); err != nil {
|
||||
if err := h.PodsetnikRepo.Obrisi(r.Context(), id); err != nil {
|
||||
http.Error(w, "Greška pri brisanju podsetnika", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -295,7 +259,7 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik,
|
||||
}
|
||||
|
||||
// admin/superadmin mogu dodeliti podsetnik drugom korisniku
|
||||
if k.Uloga == "admin" || k.Uloga == "superadmin" {
|
||||
if middleware.JeAdmin(k) {
|
||||
if kidStr := strings.TrimSpace(r.FormValue("korisnik_id")); kidStr != "" {
|
||||
if kid, err := strconv.ParseInt(kidStr, 10, 64); err == nil && kid > 0 {
|
||||
p.KorisnikID = &kid
|
||||
@@ -309,6 +273,29 @@ func parseFormuPodsetnika(r *http.Request, k *model.Korisnik) (model.Podsetnik,
|
||||
return p, ""
|
||||
}
|
||||
|
||||
// prikaziGreskuPodsetnika prikazuje formu podsetnika sa porukom o grešci
|
||||
func (h *Handler) prikaziGreskuPodsetnika(w http.ResponseWriter, r *http.Request, k *model.Korisnik, podsetnik model.Podsetnik, poruka string, izmena bool) {
|
||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||
ps.Stranica = "podsetnici"
|
||||
if izmena {
|
||||
ps.NaslovStranice = "Izmeni podsetnik"
|
||||
} else {
|
||||
ps.NaslovStranice = "Novi podsetnik"
|
||||
}
|
||||
var korisnici []model.Korisnik
|
||||
if middleware.JeAdmin(k) {
|
||||
korisnici, _ = h.KorisniciRepo.Lista(r.Context())
|
||||
}
|
||||
h.renderujFormuPodsetnika(w, podaciPodsetnikForma{
|
||||
PodaciStranice: ps,
|
||||
Podsetnik: podsetnik,
|
||||
Greska: poruka,
|
||||
Izmena: izmena,
|
||||
Korisnici: korisnici,
|
||||
})
|
||||
}
|
||||
|
||||
// renderujFormuPodsetnika renderuje HTML šablon forme za unos ili izmenu podsetnika
|
||||
func (h *Handler) renderujFormuPodsetnika(w http.ResponseWriter, podaci podaciPodsetnikForma) {
|
||||
h.renderujTemplate(w, "podsetnik_forma", podaci)
|
||||
|
||||
@@ -250,3 +250,42 @@ func (h *Handler) ProfilSacuvajPozadinuStilove(w http.ResponseWriter, r *http.Re
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Podešavanja su sačuvana.")
|
||||
http.Redirect(w, r, "/profil/tema", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// SacuvajLokalnuTemu čuva korisnikovu lokalnu temu
|
||||
func (h *Handler) SacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
|
||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||
if k == nil {
|
||||
http.Redirect(w, r, "/prijava", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "tema.lokalno") {
|
||||
http.Error(w, "Pristup odbijen", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
koristi := r.FormValue("koristi_lokalnu_temu") == "1"
|
||||
lokalnaTema := r.FormValue("lokalna_tema")
|
||||
if lokalnaTema != "tamna" && lokalnaTema != "svetla" {
|
||||
lokalnaTema = "tamna"
|
||||
}
|
||||
|
||||
if err := h.KorisniciRepo.SacuvajLokalnuTemu(r.Context(), k.ID, lokalnaTema, koristi); err != nil {
|
||||
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
middleware.SetFlash(w, r, h.DB, "uspeh", "Tema je sačuvana.")
|
||||
// vrati korisnika na stranicu odakle je došao (Referer), ili na profil kao fallback
|
||||
if ref := r.Referer(); ref != "" {
|
||||
http.Redirect(w, r, ref, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ func BezbednostHeaders() func(http.Handler) http.Handler {
|
||||
h.Set("Content-Security-Policy",
|
||||
"default-src 'self'; "+
|
||||
"style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; "+
|
||||
"script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
|
||||
// Alpine.js v3 koristi new Function() interno — unsafe-eval je neophodan; CSP build zahteva značajan refaktoring.
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
|
||||
"img-src 'self' data: blob:; "+
|
||||
"font-src 'self'; "+
|
||||
"connect-src 'self'")
|
||||
|
||||
+20
-136
@@ -4,142 +4,26 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>NTech — Konfiguracija</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #1a1a2e;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.kartica {
|
||||
background: #16213e;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
width: 410px;
|
||||
border: 1px solid #0f3460;
|
||||
}
|
||||
|
||||
.zaglavlje {
|
||||
text-align: center;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
.zaglavlje h1 {
|
||||
color: #f8e45c;
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.zaglavlje p {
|
||||
color: #a0a0b0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sekcija-naslov {
|
||||
color: #a0a0b0;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.port-opcija {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin: 0.25rem 0;
|
||||
border: 2px solid #0f3460;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.port-opcija:hover {
|
||||
border-color: #e94560;
|
||||
}
|
||||
|
||||
.port-opcija.izabran {
|
||||
border-color: #e94560;
|
||||
background: rgba(233, 69, 96, 0.1);
|
||||
}
|
||||
|
||||
.port-opcija.zauzet {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.slobodan {
|
||||
color: #2ecc71;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.zauzet-tekst {
|
||||
color: #e74c3c;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.razdvojnik {
|
||||
border: none;
|
||||
border-top: 1px solid #0f3460;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #0f3460;
|
||||
border: 2px solid #0f3460;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: #e0e0e0;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
input[type="number"]:focus {
|
||||
border-color: #e94560;
|
||||
}
|
||||
|
||||
input[type="number"]::placeholder {
|
||||
color: #606080;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 0.65rem;
|
||||
margin-top: 0.75rem;
|
||||
background: #e94560;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #c73652;
|
||||
}
|
||||
|
||||
.poruka {
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: #2ecc71;
|
||||
display: none;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #1a1a2e; display: flex; justify-content: center; align-items: center; height: 100vh; }
|
||||
.kartica { background: #16213e; padding: 2rem; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); width: 410px; border: 1px solid #0f3460; }
|
||||
.zaglavlje { text-align: center; margin-bottom: 1.3rem; }
|
||||
.zaglavlje h1 { color: #f8e45c; font-size: 1.4rem; margin-bottom: 0.25rem; }
|
||||
.zaglavlje p { color: #a0a0b0; font-size: 0.85rem; }
|
||||
.sekcija-naslov { color: #a0a0b0; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.4rem; }
|
||||
.port-opcija { display: flex; justify-content: space-between; align-items: center; padding: 0.5rem 0.75rem; margin: 0.25rem 0; border: 2px solid #0f3460; border-radius: 8px; cursor: pointer; transition: all 0.2s; color: #e0e0e0; }
|
||||
.port-opcija:hover { border-color: #e94560; }
|
||||
.port-opcija.izabran { border-color: #e94560; background: rgba(233, 69, 96, 0.1); }
|
||||
.port-opcija.zauzet { opacity: 0.5; cursor: not-allowed; }
|
||||
.slobodan { color: #2ecc71; font-size: 0.85rem; }
|
||||
.zauzet-tekst { color: #e74c3c; font-size: 0.85rem; }
|
||||
.razdvojnik { border: none; border-top: 1px solid #0f3460; margin: 0.75rem 0; }
|
||||
input[type="number"] { width: 100%; padding: 0.5rem 0.75rem; background: #0f3460; border: 2px solid #0f3460; border-radius: 8px; font-size: 0.9rem; color: #e0e0e0; outline: none; transition: border-color 0.2s; }
|
||||
input[type="number"]:focus { border-color: #e94560; }
|
||||
input[type="number"]::placeholder { color: #606080; }
|
||||
button { width: 100%; padding: 0.65rem; margin-top: 0.75rem; background: #e94560; color: white; border: none; border-radius: 8px; font-size: 0.95rem; font-weight: 600; cursor: pointer; transition: background 0.2s; }
|
||||
button:hover { background: #c73652; }
|
||||
.poruka { text-align: center; padding: 0.5rem; font-size: 0.85rem; color: #2ecc71; display: none; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
@@ -10,16 +10,7 @@
|
||||
.dozvole-tabela tbody tr:nth-child(4) { animation-delay: 0.16s; }
|
||||
.dozvole-tabela tbody tr:nth-child(5) { animation-delay: 0.20s; }
|
||||
|
||||
.matrica-modul td {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--tekst-sporedni);
|
||||
background: var(--pozadina);
|
||||
border-top: 0.5px solid var(--ivica);
|
||||
}
|
||||
.matrica-modul td { padding: 8px 16px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--tekst-sporedni); background: var(--pozadina); border-top: 0.5px solid var(--ivica); }
|
||||
|
||||
.matrica-checkbox { text-align: center; padding: 8px 16px; }
|
||||
.matrica-checkbox input[type=checkbox] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--sb-akcent); }
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
.korisnici-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.korisnici-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.nova-forma-kartica {
|
||||
animation-delay: 0.30s;
|
||||
}
|
||||
.nova-forma-kartica { animation-delay: 0.30s; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -1,74 +1,50 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "naslov"}}Istorija prijava — NTech{{end}}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{template "base" .}} {{define "naslov"}}Istorija prijava — NTech{{end}} {{define "dodatni-css"}}
|
||||
<style>
|
||||
.animiraj:nth-child(1) { animation-delay: 0.10s; }
|
||||
.animiraj:nth-child(1) { animation-delay: 0.1s; }
|
||||
.animiraj:nth-child(2) { animation-delay: 0.16s; }
|
||||
.animiraj:nth-child(3) { animation-delay: 0.22s; }
|
||||
.animiraj:nth-child(4) { animation-delay: 0.28s; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||
|
||||
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
|
||||
<a href="/admin/korisnici"
|
||||
style="display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-sporedni);font-size:13px;text-decoration:none;">
|
||||
← Nazad
|
||||
</a>
|
||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">
|
||||
Istorija prijava — {{.PrikazKorisnik.KorisnickoIme}}
|
||||
</span>
|
||||
{{end}} {{define "sadrzaj"}}
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap">
|
||||
<a href="/admin/korisnici" style="display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; border: 0.5px solid var(--ivica); border-radius: 8px; color: var(--tekst-sporedni); font-size: 13px; text-decoration: none">← Nazad</a>
|
||||
<span style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni)">Istorija prijava — {{.PrikazKorisnik.KorisnickoIme}}</span>
|
||||
</div>
|
||||
|
||||
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
|
||||
<div style="padding:16px 20px;border-bottom:0.5px solid var(--ivica);display:flex;align-items:center;justify-content:space-between;">
|
||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Poslednjih 50 pokušaja</span>
|
||||
<span style="font-size:12px;color:var(--tekst-sporedni);">Vreme prikazano po lokalnom vremenu servera</span>
|
||||
<div class="kartica animiraj" style="padding: 0; overflow: hidden">
|
||||
<div style="padding: 16px 20px; border-bottom: 0.5px solid var(--ivica); display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni)">Poslednjih 50 pokušaja</span>
|
||||
<span style="font-size: 12px; color: var(--tekst-sporedni)">Vreme prikazano po lokalnom vremenu servera</span>
|
||||
</div>
|
||||
|
||||
{{if .Istorija}}
|
||||
<div style="overflow-x:auto;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<div style="overflow-x: auto">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<thead>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<th style="padding:10px 20px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);white-space:nowrap;">Datum i vreme</th>
|
||||
<th style="padding:10px 20px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">IP adresa</th>
|
||||
<th style="padding:10px 20px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Pregledač</th>
|
||||
<th style="padding:10px 20px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Status</th>
|
||||
<th style="padding:10px 20px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Razlog</th>
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<th style="padding: 10px 20px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni); white-space: nowrap">Datum i vreme</th>
|
||||
<th style="padding: 10px 20px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">IP adresa</th>
|
||||
<th style="padding: 10px 20px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Pregledač</th>
|
||||
<th style="padding: 10px 20px; text-align: center; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Status</th>
|
||||
<th style="padding: 10px 20px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Razlog</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Istorija}}
|
||||
<tr class="animiraj" style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 20px;font-size:13px;color:var(--tekst-sporedni);white-space:nowrap;">
|
||||
{{.Vreme.Format "02.01.2006. 15:04:05"}}
|
||||
</td>
|
||||
<td style="padding:10px 20px;font-size:13px;color:var(--tekst-glavni);font-family:monospace;">
|
||||
{{.IP}}
|
||||
</td>
|
||||
<td style="padding:10px 20px;font-size:12px;color:var(--tekst-sporedni);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{.UserAgent}}
|
||||
</td>
|
||||
<td style="padding:10px 20px;text-align:center;">
|
||||
<tr class="animiraj" style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<td style="padding: 10px 20px; font-size: 13px; color: var(--tekst-sporedni); white-space: nowrap">{{.Vreme.Format "02.01.2006. 15:04:05"}}</td>
|
||||
<td style="padding: 10px 20px; font-size: 13px; color: var(--tekst-glavni); font-family: monospace">{{.IP}}</td>
|
||||
<td style="padding: 10px 20px; font-size: 12px; color: var(--tekst-sporedni); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">{{.UserAgent}}</td>
|
||||
<td style="padding: 10px 20px; text-align: center">
|
||||
{{if .Uspeh}}
|
||||
<span style="display:inline-block;padding:2px 10px;border-radius:20px;background:#f0fdf4;color:#16a34a;font-size:11px;font-weight:500;">Uspeh</span>
|
||||
<span style="display: inline-block; padding: 2px 10px; border-radius: 20px; background: #035f1f; color: #16a34a; font-size: 11px; font-weight: 500">Uspeh</span>
|
||||
{{else}}
|
||||
<span style="display:inline-block;padding:2px 10px;border-radius:20px;background:#fef2f2;color:#dc2626;font-size:11px;font-weight:500;">Neuspeh</span>
|
||||
<span style="display: inline-block; padding: 2px 10px; border-radius: 20px; background: #e21313; color: #dc2626; font-size: 11px; font-weight: 500">Neuspeh</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td style="padding:10px 20px;font-size:13px;color:var(--tekst-sporedni);">
|
||||
{{if eq .Razlog "korisnik_ne_postoji"}}Korisnik ne postoji
|
||||
{{else if eq .Razlog "nalog_neaktivan"}}Nalog neaktivan
|
||||
{{else if eq .Razlog "pogrešna_lozinka"}}Pogrešna lozinka
|
||||
{{else if eq .Razlog "ip_zaklucano"}}IP zaključano
|
||||
{{else if .Uspeh}}—
|
||||
{{else}}{{.Razlog}}
|
||||
{{end}}
|
||||
<td style="padding: 10px 20px; font-size: 13px; color: var(--tekst-sporedni)">
|
||||
{{if eq .Razlog "korisnik_ne_postoji"}}Korisnik ne postoji {{else if eq .Razlog "nalog_neaktivan"}}Nalog neaktivan {{else if eq .Razlog "pogrešna_lozinka"}}Pogrešna lozinka {{else if eq .Razlog "ip_zaklucano"}}IP zaključano {{else if .Uspeh}}— {{else}}{{.Razlog}} {{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -76,11 +52,8 @@
|
||||
</table>
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="padding:40px 20px;text-align:center;color:var(--tekst-sporedni);font-size:14px;">
|
||||
Nema zabeleženih pokušaja prijave za ovog korisnika.
|
||||
</div>
|
||||
<div style="padding: 40px 20px; text-align: center; color: var(--tekst-sporedni); font-size: 14px">Nema zabeleženih pokušaja prijave za ovog korisnika.</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "naslov"}}Moj profil — NTech{{end}}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{ template "base" . }} {{ define "naslov" }}Moj profil — NTech{{ end }}
|
||||
{{ define "dodatni-css" }}
|
||||
<style>
|
||||
.animiraj:nth-child(1) { animation-delay: 0.10s; }
|
||||
.animiraj:nth-child(1) { animation-delay: 0.1s; }
|
||||
.animiraj:nth-child(2) { animation-delay: 0.16s; }
|
||||
.animiraj:nth-child(3) { animation-delay: 0.22s; }
|
||||
.animiraj:nth-child(4) { animation-delay: 0.28s; }
|
||||
.animiraj:nth-child(5) { animation-delay: 0.34s; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
<div style="display:flex;flex-direction:column;gap:16px;max-width:560px;">
|
||||
|
||||
{{ end }}
|
||||
{{ define "sadrzaj" }}
|
||||
<div style="display: flex; flex-direction: column; gap: 16px; max-width: 560px">
|
||||
<!-- promena lozinke -->
|
||||
<div class="kartica animiraj">
|
||||
<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);">
|
||||
Promena lozinke
|
||||
</div>
|
||||
<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)">Promena lozinke</div>
|
||||
|
||||
<form method="POST" action="/admin/profil/lozinka">
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Trenutna lozinka</label>
|
||||
<input type="password" name="stara_lozinka" required style="width:100%;">
|
||||
<label style="font-size: 13px; color: var(--tekst-sporedni); display: block; margin-bottom: 6px">Trenutna lozinka</label>
|
||||
<input type="password" name="stara_lozinka" required style="width: 100%" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Nova lozinka</label>
|
||||
<input type="password" name="nova_lozinka" required minlength="8" style="width:100%;">
|
||||
<label style="font-size: 13px; color: var(--tekst-sporedni); display: block; margin-bottom: 6px">Nova lozinka</label>
|
||||
<input type="password" name="nova_lozinka" required minlength="8" style="width: 100%" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Potvrda nove lozinke</label>
|
||||
<input type="password" name="nova_lozinka_potvrda" required style="width:100%;">
|
||||
<label style="font-size: 13px; color: var(--tekst-sporedni); display: block; margin-bottom: 6px">Potvrda nove lozinke</label>
|
||||
<input type="password" name="nova_lozinka_potvrda" required style="width: 100%" />
|
||||
</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;">
|
||||
Sačuvaj novu lozinku
|
||||
</button>
|
||||
<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 novu lozinku</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -47,87 +37,75 @@
|
||||
|
||||
<!-- TOTP / 2FA -->
|
||||
<div class="kartica animiraj">
|
||||
<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);">
|
||||
Dvostepena verifikacija (2FA)
|
||||
</div>
|
||||
<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)">Dvostepena verifikacija (2FA)</div>
|
||||
|
||||
{{if .TotpURI}}
|
||||
{{ if .TotpURI }}
|
||||
<!-- postavljanje TOTP -->
|
||||
{{if eq .Greska "totp"}}
|
||||
<div class="poruka-greska" style="margin-bottom:14px;">Neispravan kod. Pokušajte ponovo.</div>
|
||||
{{end}}
|
||||
{{ if eq .Greska "totp" }}
|
||||
<div class="poruka-greska" style="margin-bottom: 14px">Neispravan kod. Pokušajte ponovo.</div>
|
||||
{{ end }}
|
||||
|
||||
<p style="font-size:13px;color:var(--tekst-sporedni);margin-bottom:16px;line-height:1.5;">
|
||||
Skenirajte QR kod u aplikaciji (Google Authenticator, Authy...), pa unesite generisani kod da potvrdite podešavanje.
|
||||
</p>
|
||||
<p style="font-size: 13px; color: var(--tekst-sporedni); margin-bottom: 16px; line-height: 1.5">Skenirajte QR kod u aplikaciji (Google Authenticator, Authy...), pa unesite generisani kod da potvrdite podešavanje.</p>
|
||||
|
||||
<div style="text-align:center;margin-bottom:16px;">
|
||||
<img src="{{.TotpQR}}" alt="QR kod" style="width:200px;height:200px;border-radius:8px;display:block;margin:0 auto;">
|
||||
<div style="text-align: center; margin-bottom: 16px">
|
||||
<img src="{{ .TotpQR }}" alt="QR kod" style="width: 200px; height: 200px; border-radius: 8px; display: block; margin: 0 auto" />
|
||||
</div>
|
||||
|
||||
<details style="margin-bottom:16px;">
|
||||
<summary style="font-size:12px;color:var(--tekst-sporedni);cursor:pointer;">Prikaži tajnu ručno</summary>
|
||||
<code style="font-size:12px;background:var(--pozadina);padding:6px 10px;border-radius:6px;display:block;margin-top:8px;word-break:break-all;color:var(--tekst-glavni);">{{.TotpTajna}}</code>
|
||||
<details style="margin-bottom: 16px">
|
||||
<summary style="font-size: 12px; color: var(--tekst-sporedni); cursor: pointer">Prikaži tajnu ručno</summary>
|
||||
<code style="font-size: 12px; background: var(--pozadina); padding: 6px 10px; border-radius: 6px; display: block; margin-top: 8px; word-break: break-all; color: var(--tekst-glavni)">{{ .TotpTajna }}</code>
|
||||
</details>
|
||||
|
||||
<form method="POST" action="/admin/profil/totp/aktiviraj">
|
||||
<input type="hidden" name="totp_tajna" value="{{.TotpTajna}}">
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Verifikacioni kod</label>
|
||||
<input type="text" name="kod" inputmode="numeric" pattern="[0-9]{6}"
|
||||
maxlength="6" required autofocus
|
||||
style="width:160px;font-size:18px;text-align:center;letter-spacing:4px;">
|
||||
<input type="hidden" name="totp_tajna" value="{{ .TotpTajna }}" />
|
||||
<div style="margin-bottom: 12px">
|
||||
<label style="font-size: 13px; color: var(--tekst-sporedni); display: block; margin-bottom: 6px">Verifikacioni kod</label>
|
||||
<input type="text" name="kod" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" required autofocus style="width: 160px; font-size: 18px; text-align: center; letter-spacing: 4px" />
|
||||
</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;">
|
||||
Potvrdi i uključi 2FA
|
||||
</button>
|
||||
<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">Potvrdi i uključi 2FA</button>
|
||||
</form>
|
||||
|
||||
{{else if .TotpAktivan}}
|
||||
{{ else if .TotpAktivan }}
|
||||
<!-- 2FA je uključena -->
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px">
|
||||
<div>
|
||||
<div style="font-size:14px;color:#16a34a;margin-bottom:4px;font-weight:500;">✓ Dvostepena verifikacija je uključena</div>
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">Prijava zahteva TOTP kod pored lozinke.</div>
|
||||
<div style="font-size: 14px; color: #16a34a; margin-bottom: 4px; font-weight: 500">✓ Dvostepena verifikacija je uključena</div>
|
||||
<div style="font-size: 13px; color: var(--tekst-sporedni)">Prijava zahteva TOTP kod pored lozinke.</div>
|
||||
</div>
|
||||
<form method="POST" action="/admin/profil/totp/deaktiviraj"
|
||||
onsubmit="return confirm('Da li ste sigurni? Ovo će isključiti dvostepenu verifikaciju.')">
|
||||
<button type="submit"
|
||||
style="padding:9px 18px;background:#dc2626;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;">
|
||||
Deaktiviraj 2FA
|
||||
</button>
|
||||
<form method="POST" action="/admin/profil/totp/deaktiviraj" onsubmit="return confirm('Da li ste sigurni? Ovo će isključiti dvostepenu verifikaciju.');">
|
||||
<button type="submit" style="padding: 9px 18px; background: #dc2626; color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer">Deaktiviraj 2FA</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
{{ else }}
|
||||
<!-- 2FA nije uključena -->
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px">
|
||||
<div>
|
||||
<div style="font-size:14px;color:var(--tekst-glavni);margin-bottom:4px;">Status: <strong>Isključena</strong></div>
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">Preporučujemo uključivanje dvostepene verifikacije.</div>
|
||||
<div style="font-size: 14px; color: var(--tekst-glavni); margin-bottom: 4px">
|
||||
Status:
|
||||
<strong>Isključena</strong>
|
||||
</div>
|
||||
<a href="/admin/profil/totp/pokreni"
|
||||
style="padding:9px 18px;background:var(--sb-akcent);color:#fff;border-radius:8px;font-size:14px;font-weight:500;text-decoration:none;">
|
||||
Podesi 2FA
|
||||
</a>
|
||||
<div style="font-size: 13px; color: var(--tekst-sporedni)">Preporučujemo uključivanje dvostepene verifikacije.</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<a href="/admin/profil/totp/pokreni" style="padding: 9px 18px; background: var(--sb-akcent); color: #fff; border-radius: 8px; font-size: 14px; font-weight: 500; text-decoration: none">Podesi 2FA</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
|
||||
{{if index .Dozvole "tema.lokalno"}}
|
||||
<div class="kartica animiraj" style="background:var(--pozadina);border:0.5px solid var(--ivica);">
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<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="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
|
||||
<span style="font-size:14px;color:var(--tekst-glavni);">
|
||||
{{ if index .Dozvole "tema.lokalno" }}
|
||||
<div class="kartica animiraj" style="background: var(--pozadina); border: 0.5px solid var(--ivica)">
|
||||
<div style="display: flex; align-items: center; gap: 10px">
|
||||
<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="3" />
|
||||
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83" />
|
||||
</svg>
|
||||
<span style="font-size: 14px; color: var(--tekst-glavni)">
|
||||
Podešavanja teme i pozadine nalaze se na
|
||||
<a href="/profil/tema" style="color:var(--sb-akcent);text-decoration:none;font-weight:500;">stranici Tema</a>.
|
||||
<a href="/profil/tema" style="color: var(--sb-akcent); text-decoration: none; font-weight: 500">stranici Tema</a>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{ end }}
|
||||
</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
@@ -1,66 +1,80 @@
|
||||
{{template "base" .}}
|
||||
{{ template "base" . }}
|
||||
|
||||
{{define "naslov"}}Dashboard — NTech{{end}}
|
||||
{{ define "naslov" }}Dashboard — NTech{{ end }}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{ define "dodatni-css" }}
|
||||
<style>
|
||||
.dash-stat:nth-child(1) { animation-delay: 0.04s; }
|
||||
.dash-stat:nth-child(2) { animation-delay: 0.10s; }
|
||||
.dash-stat:nth-child(3) { animation-delay: 0.16s; }
|
||||
.dash-stat:nth-child(4) { animation-delay: 0.22s; }
|
||||
.dash-stat:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
.dash-kartica:nth-child(1) { animation-delay: 0.20s; }
|
||||
.dash-kartica:nth-child(2) { animation-delay: 0.28s; }
|
||||
.dash-kartica:nth-child(3) { animation-delay: 0.36s; }
|
||||
</style>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
{{if .FlashGreska}}
|
||||
<div class="poruka-greska animiraj" style="margin-bottom:16px;">{{.FlashGreska}}</div>
|
||||
{{end}}
|
||||
{{ define "sadrzaj" }}
|
||||
{{ if .FlashGreska }}
|
||||
<div class="poruka-greska animiraj" style="margin-bottom:16px;">{{ .FlashGreska }}</div>
|
||||
{{ end }}
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-3 mb-6">
|
||||
<!-- stat kartice -->
|
||||
<div class="kartica dash-stat animiraj">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:#eff2ff;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#4f7ef8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#4f7ef8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{.BrojArtikala}}</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ .BrojArtikala }}</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Artikala na stanju</div>
|
||||
</div>
|
||||
|
||||
<div class="kartica dash-stat animiraj">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:#f0fdf4;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{.AktivniServisi}}</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ .AktivniServisi }}</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Aktivnih servisa</div>
|
||||
</div>
|
||||
|
||||
{{if index .Dozvole "dashboard.prihod"}}
|
||||
{{ if index .Dozvole "dashboard.prihod" }}
|
||||
<div class="kartica dash-stat animiraj">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:#fff7ed;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ea580c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ea580c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="9" cy="21" r="1" />
|
||||
<circle cx="20" cy="21" r="1" />
|
||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .PrihodOvogMeseca}} din</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ printf "%.0f" .PrihodOvogMeseca }} din</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Prihod ovog meseca</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
|
||||
<div class="kartica dash-stat animiraj">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:#fef2f2;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||||
<line x1="12" y1="9" x2="12" y2="13" />
|
||||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{.KriticnaZaliha}}</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ .KriticnaZaliha }}</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Kritično niska zaliha</div>
|
||||
</div>
|
||||
|
||||
<a href="/podsetnici" class="kartica dash-stat animiraj" style="text-decoration:none;display:block;">
|
||||
<div style="width:36px;height:36px;border-radius:8px;background:#f0f9ff;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#0284c7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#0284c7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
|
||||
</svg>
|
||||
</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{.AktivniPodsetnici}}</div>
|
||||
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{ .AktivniPodsetnici }}</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Aktivnih podsetnika</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -71,15 +85,15 @@
|
||||
<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);">Poslednji servisi</span>
|
||||
</div>
|
||||
{{range .PoslednjiServisi}}
|
||||
{{ range .PoslednjiServisi }}
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
|
||||
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{.BojaTacke}};"></div>
|
||||
<span style="font-size:13px;color:var(--tekst-glavni);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{.Uredjaj}}</span>
|
||||
<span style="font-size:11px;color:var(--tekst-sporedni);white-space:nowrap;">{{.DatumPrijema}}</span>
|
||||
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{ .BojaTacke }};"></div>
|
||||
<span style="font-size:13px;color:var(--tekst-glavni);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{ .Uredjaj }}</span>
|
||||
<span style="font-size:11px;color:var(--tekst-sporedni);white-space:nowrap;">{{ .DatumPrijema }}</span>
|
||||
</div>
|
||||
{{else}}
|
||||
{{ else }}
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Nema servisnih naloga.</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- kritične zalihe -->
|
||||
@@ -88,15 +102,15 @@
|
||||
<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:rgba(220,38,38,0.15);color:#ef4444;font-weight:500;">Upozorenje</span>
|
||||
</div>
|
||||
{{range .KriticneZalihe}}
|
||||
{{ range .KriticneZalihe }}
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
|
||||
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{.BojaTacke}};"></div>
|
||||
<span style="font-size:13px;color:var(--tekst-glavni);flex:1;">{{.Naziv}}</span>
|
||||
<span style="font-size:12px;color:var(--tekst-sporedni);">{{.Kolicina}} kom</span>
|
||||
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{ .BojaTacke }};"></div>
|
||||
<span style="font-size:13px;color:var(--tekst-glavni);flex:1;">{{ .Naziv }}</span>
|
||||
<span style="font-size:12px;color:var(--tekst-sporedni);">{{ .Kolicina }} kom</span>
|
||||
</div>
|
||||
{{else}}
|
||||
{{ else }}
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Sve zalihe su uredne.</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- poslednje prodaje -->
|
||||
@@ -104,22 +118,22 @@
|
||||
<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);">Poslednje prodaje</span>
|
||||
</div>
|
||||
{{range .PoslednjeProdaje}}
|
||||
{{ range .PoslednjeProdaje }}
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div style="font-size:13px;color:var(--tekst-glavni);font-family:monospace;">{{.BrojNaloga}}</div>
|
||||
{{if .KlijentNaziv}}
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{.KlijentNaziv}}</div>
|
||||
{{end}}
|
||||
<div style="font-size:13px;color:var(--tekst-glavni);font-family:monospace;">{{ .BrojNaloga }}</div>
|
||||
{{ if .KlijentNaziv }}
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{ .KlijentNaziv }}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div style="text-align:right;flex-shrink:0;">
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .Ukupno}} din</div>
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;">{{.Datum}}</div>
|
||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{ printf "%.0f" .Ukupno }} din</div>
|
||||
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;">{{ .Datum }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{ else }}
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Nema prodajnih naloga.</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{ end }}
|
||||
@@ -5,8 +5,6 @@
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
/* stagger — svaki red se pojavljuje malo kasnije */
|
||||
.dobavljaci-tabela tbody tr:nth-child(1) { animation-delay: 0.04s; }
|
||||
.dobavljaci-tabela tbody tr:nth-child(2) { animation-delay: 0.08s; }
|
||||
.dobavljaci-tabela tbody tr:nth-child(3) { animation-delay: 0.12s; }
|
||||
@@ -17,13 +15,7 @@
|
||||
.dobavljaci-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
|
||||
.dobavljaci-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.dobavljaci-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.dobavljaci-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dobavljaci-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.dobavljac-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.dobavljac-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.dobavljac-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
@@ -31,8 +23,13 @@
|
||||
.dobavljac-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dobavljaci-tabela { display: none; }
|
||||
.dobavljaci-kartice { display: flex; }
|
||||
.dobavljaci-tabela {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dobavljaci-kartice {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
@@ -5,88 +5,22 @@
|
||||
{{define "dodatni-css"}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
.izv-naslov {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.toggle-red {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.izv-naslov { font-size: 13px; font-weight: 500; color: var(--tekst-sporedni); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 14px; }
|
||||
.toggle-red { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.toggle-switch { position: relative; display: inline-block; width: 40px; height: 22px; flex-shrink: 0; }
|
||||
.toggle-switch input { opacity: 0; width: 0; height: 0; }
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--ivica);
|
||||
border-radius: 22px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.toggle-slider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.toggle-slider { position: absolute; inset: 0; background: var(--ivica); border-radius: 22px; cursor: pointer; transition: background 0.2s; }
|
||||
.toggle-slider::before { content: ''; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform 0.2s; }
|
||||
.toggle-switch input:checked + .toggle-slider { background: var(--sb-akcent); }
|
||||
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(18px); }
|
||||
|
||||
.rang-broj {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: var(--pozadina);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--tekst-sporedni);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.rang-broj { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: var(--pozadina); font-size: 11px; font-weight: 600; color: var(--tekst-sporedni); flex-shrink: 0; }
|
||||
.rang-broj.zlatni { background: #fef3c7; color: #92400e; }
|
||||
|
||||
.badge-dana {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-dana { display: inline-block; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 500; white-space: nowrap; }
|
||||
.badge-dana.upozorenje { background: #fffbeb; color: #b45309; }
|
||||
.badge-dana.kritican { background: #fef2f2; color: #dc2626; }
|
||||
|
||||
.izv-sekcija:nth-child(1) { animation-delay: 0.04s; }
|
||||
.izv-sekcija:nth-child(2) { animation-delay: 0.12s; }
|
||||
.izv-sekcija:nth-child(3) { animation-delay: 0.20s; }
|
||||
|
||||
.izv-grid-kartica:nth-child(1) { animation-delay: 0.24s; }
|
||||
.izv-grid-kartica:nth-child(2) { animation-delay: 0.32s; }
|
||||
</style>
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
.kat-forma-kartica {
|
||||
animation-delay: 0.04s;
|
||||
}
|
||||
|
||||
.kat-forma-kartica { animation-delay: 0.04s; }
|
||||
.kat-red:nth-child(1) { animation-delay: 0.12s; }
|
||||
.kat-red:nth-child(2) { animation-delay: 0.18s; }
|
||||
.kat-red:nth-child(3) { animation-delay: 0.24s; }
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
.klijenti-tabela tbody tr:nth-child(1) { animation-delay: 0.04s; }
|
||||
.klijenti-tabela tbody tr:nth-child(2) { animation-delay: 0.08s; }
|
||||
.klijenti-tabela tbody tr:nth-child(3) { animation-delay: 0.12s; }
|
||||
@@ -16,23 +15,13 @@
|
||||
.klijenti-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
|
||||
.klijenti-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.klijenti-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.klijenti-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.klijenti-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.klijent-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.klijent-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.klijent-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.klijent-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.klijent-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.klijenti-tabela { display: none; }
|
||||
.klijenti-kartice { display: flex; }
|
||||
}
|
||||
@media (max-width: 768px) { .klijenti-tabela { display: none; } .klijenti-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -14,23 +14,13 @@
|
||||
.magacin-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
|
||||
.magacin-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.magacin-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.magacin-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.magacin-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.magacin-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.magacin-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.magacin-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.magacin-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.magacin-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.magacin-tabela { display: none; }
|
||||
.magacin-kartice { display: flex; }
|
||||
}
|
||||
@media (max-width: 768px) { .magacin-tabela { display: none; } .magacin-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -7,23 +7,13 @@
|
||||
.detalji-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.detalji-kartica:nth-child(2) { animation-delay: 0.12s; }
|
||||
.detalji-kartica:nth-child(3) { animation-delay: 0.20s; }
|
||||
|
||||
.stavke-tabela tbody tr:nth-child(1) { animation-delay: 0.16s; }
|
||||
.stavke-tabela tbody tr:nth-child(2) { animation-delay: 0.20s; }
|
||||
.stavke-tabela tbody tr:nth-child(3) { animation-delay: 0.24s; }
|
||||
.stavke-tabela tbody tr:nth-child(4) { animation-delay: 0.28s; }
|
||||
.stavke-tabela tbody tr:nth-child(5) { animation-delay: 0.32s; }
|
||||
|
||||
.stavke-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stavke-tabela-wrapper { display: none; }
|
||||
.stavke-kartice { display: flex; }
|
||||
}
|
||||
.stavke-kartice { display: none; flex-direction: column; gap: 10px; }
|
||||
@media (max-width: 768px) { .stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -4,22 +4,12 @@
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
@keyframes modalIn {
|
||||
from { opacity: 0; transform: translateY(-16px) scale(0.97); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes modalIn { from { opacity: 0; transform: translateY(-16px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
||||
.forma-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.forma-kartica:nth-child(2) { animation-delay: 0.12s; }
|
||||
|
||||
.greska-animacija { animation: shake 0.4s ease; }
|
||||
|
||||
.modal-sadrzaj { animation: modalIn 0.25s ease forwards; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stavke-tabela-wrapper { display: none; }
|
||||
.stavke-kartice { display: flex !important; }
|
||||
}
|
||||
@media (max-width: 768px) { .stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex !important; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
.nabavke-tabela tbody tr:nth-child(1) { animation-delay: 0.04s; }
|
||||
.nabavke-tabela tbody tr:nth-child(2) { animation-delay: 0.08s; }
|
||||
.nabavke-tabela tbody tr:nth-child(3) { animation-delay: 0.12s; }
|
||||
@@ -16,23 +15,13 @@
|
||||
.nabavke-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
|
||||
.nabavke-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.nabavke-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.nabavke-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.nabavke-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.nabavka-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.nabavka-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.nabavka-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.nabavka-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.nabavka-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nabavke-tabela { display: none; }
|
||||
.nabavke-kartice { display: flex; }
|
||||
}
|
||||
@media (max-width: 768px) { .nabavke-tabela { display: none; } .nabavke-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -8,74 +8,17 @@
|
||||
.animiraj:nth-child(2) { animation-delay: 0.16s; }
|
||||
.animiraj:nth-child(3) { animation-delay: 0.22s; }
|
||||
.animiraj:nth-child(4) { animation-delay: 0.28s; }
|
||||
|
||||
/* dvostupčani raspored za app pozadinu */
|
||||
.app-poz-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 680px) {
|
||||
.app-poz-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* custom file input */
|
||||
.custom-file-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--pozadina);
|
||||
border: 0.5px solid var(--ivica);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--tekst-sporedni);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.app-poz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||||
@media (max-width: 680px) { .app-poz-grid { grid-template-columns: 1fr; } }
|
||||
.custom-file-label { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 8px; font-size: 13px; color: var(--tekst-sporedni); cursor: pointer; transition: border-color 0.2s; min-width: 0; overflow: hidden; }
|
||||
.custom-file-label:hover { border-color: var(--sb-akcent); color: var(--tekst-glavni); }
|
||||
.custom-file-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
animation: toastIn 0.3s ease forwards;
|
||||
max-width: 340px;
|
||||
}
|
||||
.toast-greska {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 0.5px solid #fca5a5;
|
||||
}
|
||||
.toast-uspeh {
|
||||
background: #f0fdf4;
|
||||
color: #16a34a;
|
||||
border: 0.5px solid #86efac;
|
||||
}
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes toastOut {
|
||||
from { opacity: 1; transform: translateY(0); }
|
||||
to { opacity: 0; transform: translateY(12px); }
|
||||
}
|
||||
.toast.nestaje {
|
||||
animation: toastOut 0.3s ease forwards;
|
||||
}
|
||||
.toast { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-radius: 10px; font-size: 13px; font-weight: 500; box-shadow: 0 4px 20px rgba(0,0,0,0.15); animation: toastIn 0.3s ease forwards; max-width: 340px; }
|
||||
.toast-greska { background: #fef2f2; color: #dc2626; border: 0.5px solid #fca5a5; }
|
||||
.toast-uspeh { background: #f0fdf4; color: #16a34a; border: 0.5px solid #86efac; }
|
||||
@keyframes toastIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes toastOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(12px); } }
|
||||
.toast.nestaje { animation: toastOut 0.3s ease forwards; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -7,16 +7,8 @@
|
||||
.animiraj:nth-child(1) { animation-delay: 0.10s; }
|
||||
.animiraj:nth-child(2) { animation-delay: 0.16s; }
|
||||
.animiraj:nth-child(3) { animation-delay: 0.22s; }
|
||||
|
||||
.app-poz-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 680px) {
|
||||
.app-poz-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
.app-poz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
|
||||
@media (max-width: 680px) { .app-poz-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -7,43 +7,12 @@
|
||||
.animiraj:nth-child(1) { animation-delay: 0.10s; }
|
||||
.animiraj:nth-child(2) { animation-delay: 0.16s; }
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
animation: toastIn 0.3s ease forwards;
|
||||
max-width: 340px;
|
||||
}
|
||||
.toast-greska {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border: 0.5px solid #fca5a5;
|
||||
}
|
||||
.toast-uspeh {
|
||||
background: #f0fdf4;
|
||||
color: #16a34a;
|
||||
border: 0.5px solid #86efac;
|
||||
}
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes toastOut {
|
||||
from { opacity: 1; transform: translateY(0); }
|
||||
to { opacity: 0; transform: translateY(12px); }
|
||||
}
|
||||
.toast.nestaje {
|
||||
animation: toastOut 0.3s ease forwards;
|
||||
}
|
||||
.toast { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-radius: 10px; font-size: 13px; font-weight: 500; box-shadow: 0 4px 20px rgba(0,0,0,0.15); animation: toastIn 0.3s ease forwards; max-width: 340px; }
|
||||
.toast-greska { background: #fef2f2; color: #dc2626; border: 0.5px solid #fca5a5; }
|
||||
.toast-uspeh { background: #f0fdf4; color: #16a34a; border: 0.5px solid #86efac; }
|
||||
@keyframes toastIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes toastOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(12px); } }
|
||||
.toast.nestaje { animation: toastOut 0.3s ease forwards; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -17,26 +17,14 @@
|
||||
.pod-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.pod-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.pod-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pod-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.pod-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.pod-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.pod-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.pod-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.pod-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
.red-prekoracen td {
|
||||
background: rgba(207, 87, 87, 0.06);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pod-tabela { display: none; }
|
||||
.pod-kartice { display: flex; }
|
||||
}
|
||||
.red-prekoracen td { background: rgba(207, 87, 87, 0.06); }
|
||||
@media (max-width: 768px) { .pod-tabela { display: none; } .pod-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
<style>
|
||||
.greska-animacija {
|
||||
animation: shake 0.4s ease;
|
||||
}
|
||||
.greska-animacija { animation: shake 0.4s ease; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -1,115 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="sr">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prijava — NTech</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: #0f1117;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
}
|
||||
.kartica {
|
||||
background: #1a1d27;
|
||||
border: 0.5px solid #2d3148;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
{{if .LoginPozadina}}
|
||||
.kartica {
|
||||
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;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||
}
|
||||
{{end}}
|
||||
.logo {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.logo-naziv {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
.logo-podnazlov {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 4px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 16px; position: relative; }
|
||||
.kartica { background: #1a1d27; border: 0.5px solid #2d3148; border-radius: 16px; padding: 40px; width: 100%; max-width: 380px; }
|
||||
{{if .LoginPozadina}}.kartica { 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; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); }{{end}}
|
||||
.logo { text-align: center; margin-bottom: 32px; }
|
||||
.logo-naziv { font-size: 22px; font-weight: 600; color: #fff; letter-spacing: -0.3px; }
|
||||
.logo-podnazlov { font-size: 13px; color: #6b7280; margin-top: 4px; }
|
||||
h1 { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 24px; }
|
||||
.polje { margin-bottom: 16px; }
|
||||
label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: #0f1117;
|
||||
border: 0.5px solid #2d3148;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
label { display: block; font-size: 13px; color: #9ca3af; margin-bottom: 6px; }
|
||||
input { width: 100%; padding: 8px 12px; background: #0f1117; border: 0.5px solid #2d3148; border-radius: 8px; font-size: 14px; color: #fff; outline: none; transition: border-color 0.2s; }
|
||||
input:focus { border-color: #e53e3e; }
|
||||
.dugme {
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
background: #e53e3e;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.dugme { width: 100%; padding: 11px; background: #e53e3e; color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; margin-top: 8px; transition: opacity 0.2s; }
|
||||
.dugme:hover { opacity: 0.88; }
|
||||
.greska {
|
||||
background: #fef2f2;
|
||||
border: 0.5px solid #fca5a5;
|
||||
color: #dc2626;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.uspeh {
|
||||
background: #f0fdf4;
|
||||
border: 0.5px solid #86efac;
|
||||
color: #16a34a;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.greska { background: #fef2f2; border: 0.5px solid #fca5a5; color: #dc2626; border-radius: 8px; padding: 10px 14px; font-size: 13px; margin-bottom: 20px; }
|
||||
.uspeh { background: #f0fdf4; border: 0.5px solid #86efac; color: #16a34a; border-radius: 8px; padding: 10px 14px; font-size: 13px; margin-bottom: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
{{if .LoginPozadina}}
|
||||
<div style="position:fixed;inset:0;z-index:-1;background-image:url('{{.LoginPozadina}}');background-size:cover;background-position:center;{{if ne .LoginPozadinaBlurPozadine "0"}}filter:blur({{.LoginPozadinaBlurPozadine}}px);transform:scale(1.05);{{end}}"></div>
|
||||
<div style="position:fixed;inset:0;z-index:-1;background-image:url('{{.LoginPozadina}}');background-size:cover;background-position:center;{{if ne .LoginPozadinaBlurPozadine " 0"}}filter:blur({{.LoginPozadinaBlurPozadine}}px);transform:scale(1.05);{{end}}"></div>
|
||||
<div style="position:fixed;inset:0;z-index:0;background:rgba(0,0,0,{{.LoginPozadinaOpacity}}%);"></div>
|
||||
<div style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:1;padding:16px;">
|
||||
{{end}}
|
||||
@@ -141,13 +57,11 @@
|
||||
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
|
||||
<div class="polje">
|
||||
<label for="korisnicko_ime">Korisničko ime</label>
|
||||
<input type="text" id="korisnicko_ime" name="korisnicko_ime"
|
||||
autocomplete="username" autofocus required>
|
||||
<input type="text" id="korisnicko_ime" name="korisnicko_ime" autocomplete="username" autofocus required>
|
||||
</div>
|
||||
<div class="polje">
|
||||
<label for="lozinka">Lozinka</label>
|
||||
<input type="password" id="lozinka" name="lozinka"
|
||||
autocomplete="current-password" required>
|
||||
<input type="password" id="lozinka" name="lozinka" autocomplete="current-password" required>
|
||||
</div>
|
||||
<button type="submit" class="dugme">Prijavi se</button>
|
||||
</form>
|
||||
@@ -156,5 +70,5 @@
|
||||
{{if .LoginPozadina}}
|
||||
</div>
|
||||
{{end}}
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,103 +1,71 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "naslov"}}Prodaja — NTech{{end}}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{template "base" .}} {{define "naslov"}}Prodaja — NTech{{end}} {{define "dodatni-css"}}
|
||||
<style>
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
.prodaja-tabela tbody tr:nth-child(1) { animation-delay: 0.04s; }
|
||||
.prodaja-tabela tbody tr:nth-child(2) { animation-delay: 0.08s; }
|
||||
.prodaja-tabela tbody tr:nth-child(3) { animation-delay: 0.12s; }
|
||||
.prodaja-tabela tbody tr:nth-child(4) { animation-delay: 0.16s; }
|
||||
.prodaja-tabela tbody tr:nth-child(5) { animation-delay: 0.20s; }
|
||||
.prodaja-tabela tbody tr:nth-child(5) { animation-delay: 0.2s; }
|
||||
.prodaja-tabela tbody tr:nth-child(6) { animation-delay: 0.24s; }
|
||||
.prodaja-tabela tbody tr:nth-child(7) { animation-delay: 0.28s; }
|
||||
.prodaja-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
|
||||
.prodaja-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.prodaja-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.prodaja-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.prodaja-tabela tbody tr:nth-child(10) { animation-delay: 0.4s; }
|
||||
.prodaja-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.prodaja-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.prodaja-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.prodaja-kartica:nth-child(2) { animation-delay: 0.1s; }
|
||||
.prodaja-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.prodaja-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.prodaja-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.prodaja-tabela { display: none; }
|
||||
.prodaja-kartice { display: flex; }
|
||||
}
|
||||
@media (max-width: 768px) { .prodaja-tabela { display: none; } .prodaja-kartice { display: flex; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||
|
||||
{{end}} {{define "sadrzaj"}}
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
{{if .Sacuvano}}
|
||||
<div class="poruka-uspeh poruka-animacija">Prodaja je uspešno sačuvana.</div>
|
||||
{{end}}
|
||||
{{if .Obrisan}}
|
||||
{{end}} {{if .Obrisan}}
|
||||
<div class="poruka-uspeh poruka-animacija">Prodajni nalog je uspešno obrisan.</div>
|
||||
{{end}}
|
||||
|
||||
<!-- zaglavlje sa pretragom i dugmetom -->
|
||||
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
||||
<form method="GET" action="/prodaja" style="display:flex;gap:8px;flex:1;min-width:200px;">
|
||||
<input type="text" name="pretraga" value="{{.Pretraga}}"
|
||||
placeholder="Pretraži po broju naloga..."
|
||||
style="flex:1;">
|
||||
<button type="submit" class="btn-sekundarno" style="white-space:nowrap;">Pretraži</button>
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap">
|
||||
<form method="GET" action="/prodaja" style="display: flex; gap: 8px; flex: 1; min-width: 200px">
|
||||
<input type="text" name="pretraga" value="{{.Pretraga}}" placeholder="Pretraži po broju naloga..." style="flex: 1" />
|
||||
<button type="submit" class="btn-sekundarno" style="white-space: nowrap">Pretraži</button>
|
||||
{{if .Pretraga}}
|
||||
<a href="/prodaja" class="btn-sekundarno" style="white-space:nowrap;">✕ Resetuj</a>
|
||||
<a href="/prodaja" class="btn-sekundarno" style="white-space: nowrap">✕ Resetuj</a>
|
||||
{{end}}
|
||||
</form>
|
||||
<a href="/prodaja/nova" class="btn-primarno">+ Nova prodaja</a>
|
||||
</div>
|
||||
|
||||
<!-- desktop tabela -->
|
||||
<div class="kartica prodaja-tabela animiraj" style="padding:0;overflow:hidden;">
|
||||
<div style="overflow-x:auto;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<div class="kartica prodaja-tabela animiraj" style="padding: 0; overflow: hidden">
|
||||
<div style="overflow-x: auto">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<thead>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Broj naloga</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Datum</th>
|
||||
<th style="padding:12px 16px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Klijent</th>
|
||||
<th style="padding:12px 16px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Ukupno</th>
|
||||
<th style="padding:12px 16px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Akcije</th>
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Broj naloga</th>
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Datum</th>
|
||||
<th style="padding: 12px 16px; text-align: left; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Klijent</th>
|
||||
<th style="padding: 12px 16px; text-align: right; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Ukupno</th>
|
||||
<th style="padding: 12px 16px; text-align: center; font-size: 12px; font-weight: 500; color: var(--tekst-sporedni)">Akcije</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Nalozi}}
|
||||
<tr class="animiraj red-tabele">
|
||||
<td style="padding:12px 16px;font-size:13px;font-family:monospace;color:var(--tekst-glavni);">
|
||||
{{.BrojNaloga}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--tekst-sporedni);white-space:nowrap;">
|
||||
{{.Datum.Format "02.01.2006."}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}—{{end}}
|
||||
</td>
|
||||
<td style="padding:12px 16px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
{{printf "%.2f" .Ukupno}} din
|
||||
</td>
|
||||
<td style="padding:12px 16px;text-align:center;">
|
||||
<div style="display:flex;align-items:center;justify-content:center;gap:8px;">
|
||||
<td style="padding: 12px 16px; font-size: 13px; font-family: monospace; color: var(--tekst-glavni)">{{.BrojNaloga}}</td>
|
||||
<td style="padding: 12px 16px; font-size: 13px; color: var(--tekst-sporedni); white-space: nowrap">{{.Datum.Format "02.01.2006."}}</td>
|
||||
<td style="padding: 12px 16px; font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}—{{end}}</td>
|
||||
<td style="padding: 12px 16px; text-align: right; font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<td style="padding: 12px 16px; text-align: center">
|
||||
<div style="display: flex; align-items: center; justify-content: center; gap: 8px">
|
||||
<a href="/prodaja/{{.ID}}" class="btn-primarno-malo">Detalji</a>
|
||||
{{if index $.Dozvole "prodaja.storno"}}
|
||||
<form method="POST" action="/prodaja/storno/{{.ID}}" style="margin:0;padding:0;">
|
||||
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}">
|
||||
<button type="submit" class="btn-obrisi-malo"
|
||||
data-potvrda="Da li ste sigurni da želite da stornirate ovaj nalog? Artikli će biti vraćeni na stanje.">
|
||||
Storno
|
||||
</button>
|
||||
<form method="POST" action="/prodaja/storno/{{.ID}}" style="margin: 0; padding: 0">
|
||||
<input type="hidden" name="_csrf" value="{{$.CsrfToken}}" />
|
||||
<button type="submit" class="btn-obrisi-malo" data-potvrda="Da li ste sigurni da želite da stornirate ovaj nalog? Artikli će biti vraćeni na stanje.">Storno</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -105,11 +73,9 @@
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<td colspan="5" style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
{{if $.Pretraga}}
|
||||
Nema naloga koji odgovaraju pretrazi.
|
||||
{{else}}
|
||||
Nema prodajnih naloga. <a href="/prodaja/nova" style="color:var(--sb-akcent);">Dodaj prvu prodaju.</a>
|
||||
<td colspan="5" style="padding: 32px; text-align: center; font-size: 14px; color: var(--tekst-sporedni)">
|
||||
{{if $.Pretraga}} Nema naloga koji odgovaraju pretrazi. {{else}} Nema prodajnih naloga.
|
||||
<a href="/prodaja/nova" style="color: var(--sb-akcent)">Dodaj prvu prodaju.</a>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -123,34 +89,23 @@
|
||||
<div class="prodaja-kartice">
|
||||
{{range .Nalozi}}
|
||||
<div class="kartica prodaja-kartica animiraj">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:10px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 10px">
|
||||
<div>
|
||||
<div style="font-size:13px;font-family:monospace;color:var(--tekst-glavni);">{{.BrojNaloga}}</div>
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);margin-top:2px;">
|
||||
{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}Bez klijenta{{end}}
|
||||
<div style="font-size: 13px; font-family: monospace; color: var(--tekst-glavni)">{{.BrojNaloga}}</div>
|
||||
<div style="font-size: 14px; font-weight: 500; color: var(--tekst-glavni); margin-top: 2px">{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}Bez klijenta{{end}}</div>
|
||||
<div style="font-size: 12px; color: var(--tekst-sporedni); margin-top: 2px">{{.Datum.Format "02.01.2006."}}</div>
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">
|
||||
{{.Datum.Format "02.01.2006."}}
|
||||
<div style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni); white-space: nowrap">{{printf "%.2f" .Ukupno}} din</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);white-space:nowrap;">
|
||||
{{printf "%.2f" .Ukupno}} din
|
||||
</div>
|
||||
</div>
|
||||
<a href="/prodaja/{{.ID}}" class="btn-primarno-malo" style="justify-content:center;width:100%;box-sizing:border-box;">
|
||||
Detalji
|
||||
</a>
|
||||
<a href="/prodaja/{{.ID}}" class="btn-primarno-malo" style="justify-content: center; width: 100%; box-sizing: border-box">Detalji</a>
|
||||
</div>
|
||||
{{else}}
|
||||
<div style="padding:32px;text-align:center;font-size:14px;color:var(--tekst-sporedni);">
|
||||
{{if $.Pretraga}}
|
||||
Nema naloga koji odgovaraju pretrazi.
|
||||
{{else}}
|
||||
Nema prodajnih naloga. <a href="/prodaja/nova" style="color:var(--sb-akcent);">Dodaj prvu prodaju.</a>
|
||||
<div style="padding: 32px; text-align: center; font-size: 14px; color: var(--tekst-sporedni)">
|
||||
{{if $.Pretraga}} Nema naloga koji odgovaraju pretrazi. {{else}} Nema prodajnih naloga.
|
||||
<a href="/prodaja/nova" style="color: var(--sb-akcent)">Dodaj prvu prodaju.</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -1,94 +1,267 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "naslov"}}Detalji prodaje — NTech{{end}}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{template "base" .}} {{define "naslov"}}Detalji prodaje — NTech{{end}} {{define
|
||||
"dodatni-css"}}
|
||||
<style>
|
||||
.detalji-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.detalji-kartica:nth-child(2) { animation-delay: 0.12s; }
|
||||
.detalji-kartica:nth-child(3) { animation-delay: 0.20s; }
|
||||
|
||||
.detalji-kartica:nth-child(3) { animation-delay: 0.2s; }
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||
|
||||
{{end}} {{define "sadrzaj"}}
|
||||
<div style="display: flex; flex-direction: column; gap: 16px">
|
||||
{{if .Sacuvano}}
|
||||
<div class="poruka-uspeh poruka-animacija">Prodaja je uspešno sačuvana.</div>
|
||||
{{end}}
|
||||
|
||||
<!-- nazad dugme -->
|
||||
<a href="/prodaja" class="nazad-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true">
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
Nazad na prodaju
|
||||
</a>
|
||||
|
||||
<!-- zaglavlje naloga -->
|
||||
<div class="kartica detalji-kartica animiraj">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);">
|
||||
<span style="font-size:20px;font-weight:600;color:var(--tekst-glavni);font-family:monospace;">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 0.5px solid var(--ivica);
|
||||
">
|
||||
<span
|
||||
style="
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--tekst-glavni);
|
||||
font-family: monospace;
|
||||
">
|
||||
{{.Nalog.BrojNaloga}}
|
||||
</span>
|
||||
<a href="/prodaja/{{.Nalog.ID}}/stampa" target="_blank" class="btn-sekundarno" style="gap:6px;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||
<a
|
||||
href="/prodaja/{{.Nalog.ID}}/stampa"
|
||||
target="_blank"
|
||||
class="btn-sekundarno"
|
||||
style="gap: 6px">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true">
|
||||
<polyline points="6 9 6 2 18 2 18 9" />
|
||||
<path
|
||||
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
|
||||
<rect x="6" y="14" width="12" height="8" />
|
||||
</svg>
|
||||
Štampaj
|
||||
</a>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(140px, 1fr));gap:16px;">
|
||||
<div
|
||||
style="
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 16px;
|
||||
">
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Datum prodaje</div>
|
||||
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.Datum.Format "02.01.2006. u 15:04"}}</div>
|
||||
<div
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Datum prodaje
|
||||
</div>
|
||||
<div style="font-size: 14px; color: var(--tekst-glavni)">
|
||||
{{.Nalog.Datum.Format "02.01.2006. u 15:04"}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Klijent</div>
|
||||
<div style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
<div
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Klijent
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 14px; font-weight: 500; color: var(--tekst-glavni)">
|
||||
{{if .KlijentNaziv}}{{.KlijentNaziv}}{{else}}—{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Ukupno</div>
|
||||
<div style="font-size:20px;font-weight:600;color:var(--sb-akcent);">{{printf "%.2f" .Nalog.Ukupno}} din</div>
|
||||
<div
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Ukupno
|
||||
</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: var(--sb-akcent)">
|
||||
{{printf "%.2f" .Nalog.Ukupno}} din
|
||||
</div>
|
||||
</div>
|
||||
{{if .Nalog.Napomena}}
|
||||
<div style="grid-column:1/-1;">
|
||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Napomena</div>
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">{{.Nalog.Napomena}}</div>
|
||||
<div style="grid-column: 1/-1">
|
||||
<div
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Napomena
|
||||
</div>
|
||||
<div style="font-size: 13px; color: var(--tekst-sporedni)">
|
||||
{{.Nalog.Napomena}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- stavke -->
|
||||
<div class="kartica detalji-kartica animiraj" style="padding:0;overflow:hidden;">
|
||||
<div style="padding:16px 20px;border-bottom:0.5px solid var(--ivica);">
|
||||
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Stavke</span>
|
||||
<div
|
||||
class="kartica detalji-kartica animiraj"
|
||||
style="padding: 0; overflow: hidden">
|
||||
<div style="padding: 16px 20px; border-bottom: 0.5px solid var(--ivica)">
|
||||
<span
|
||||
style="font-size: 15px; font-weight: 500; color: var(--tekst-glavni)">Stavke</span>
|
||||
</div>
|
||||
<div style="overflow-x:auto;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<div style="overflow-x: auto">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<thead>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<th style="padding:10px 20px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Artikal</th>
|
||||
<th style="padding:10px 20px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Količina</th>
|
||||
<th style="padding:10px 20px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:140px;">Cena/kom</th>
|
||||
<th style="padding:10px 20px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:120px;">Ukupno</th>
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<th
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
">
|
||||
Artikal
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 90px;
|
||||
">
|
||||
Količina
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 140px;
|
||||
">
|
||||
Cena/kom
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 120px;
|
||||
">
|
||||
Ukupno
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Stavke}}
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:10px 20px;font-size:14px;color:var(--tekst-glavni);">{{.ArtikalNaziv}}</td>
|
||||
<td style="padding:10px 20px;text-align:center;font-size:14px;color:var(--tekst-glavni);">{{.Kolicina}}</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;color:var(--tekst-sporedni);">{{printf "%.2f" .CenaPoKomadu}} din</td>
|
||||
<td style="padding:10px 20px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.2f" .Ukupno}} din</td>
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<td
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
{{.ArtikalNaziv}}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
{{.Kolicina}}
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
color: var(--tekst-sporedni);
|
||||
">
|
||||
{{printf "%.2f" .CenaPoKomadu}} din
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
padding: 10px 20px;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
{{printf "%.2f" .Ukupno}} din
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);">
|
||||
<td colspan="3" style="padding:12px 20px;text-align:right;font-size:13px;font-weight:500;color:var(--tekst-sporedni);">Ukupno:</td>
|
||||
<td style="padding:12px 20px;text-align:right;font-size:16px;font-weight:600;color:var(--sb-akcent);">{{printf "%.2f" .Nalog.Ukupno}} din</td>
|
||||
<tr style="border-top: 0.5px solid var(--ivica)">
|
||||
<td
|
||||
colspan="3"
|
||||
style="
|
||||
padding: 12px 20px;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
">
|
||||
Ukupno:
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
padding: 12px 20px;
|
||||
text-align: right;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--sb-akcent);
|
||||
">
|
||||
{{printf "%.2f" .Nalog.Ukupno}} din
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -97,16 +270,29 @@
|
||||
|
||||
{{if index $.Dozvole "prodaja.obrisi"}}
|
||||
<!-- zona za brisanje -->
|
||||
<div class="kartica detalji-kartica animiraj" style="border-color:#dc262633;">
|
||||
<div style="display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;">
|
||||
<div style="flex:1;min-width:200px;">
|
||||
<div style="font-size:14px;font-weight:500;color:#dc2626;margin-bottom:4px;">Brisanje naloga</div>
|
||||
<div style="font-size:13px;color:var(--tekst-sporedni);">
|
||||
Brisanje je trajno. Količine artikala biće vraćene na stanje u magacinu.
|
||||
<div class="kartica detalji-kartica animiraj" style="border-color: #dc262633">
|
||||
<div
|
||||
style="display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap">
|
||||
<div style="flex: 1; min-width: 200px">
|
||||
<div
|
||||
style="
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #dc2626;
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Brisanje naloga
|
||||
</div>
|
||||
<div style="font-size: 13px; color: var(--tekst-sporedni)">
|
||||
Brisanje je trajno. Količine artikala biće vraćene na stanje u
|
||||
magacinu.
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="/prodaja/obrisi/{{.Nalog.ID}}">
|
||||
<button type="submit" class="btn-primarno" style="background:#dc2626;"
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primarno"
|
||||
style="background: #dc2626"
|
||||
data-potvrda="Da li ste sigurni da želite da obrišete nalog {{.Nalog.BrojNaloga}}? Količine artikala biće vraćene na stanje.">
|
||||
Obriši nalog
|
||||
</button>
|
||||
@@ -114,6 +300,5 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -1,27 +1,20 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "naslov"}}Nova prodaja — NTech{{end}}
|
||||
|
||||
{{define "dodatni-css"}}
|
||||
{{template "base" .}} {{define "naslov"}}Nova prodaja — NTech{{end}} {{define
|
||||
"dodatni-css"}}
|
||||
<style>
|
||||
.forma-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.forma-kartica:nth-child(2) { animation-delay: 0.12s; }
|
||||
|
||||
.greska-animacija { animation: shake 0.4s ease; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stavke-tabela-wrapper { display: none; }
|
||||
.stavke-kartice { display: flex !important; }
|
||||
}
|
||||
@media (max-width: 768px) { .stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex !important; } }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "sadrzaj"}}
|
||||
{{end}} {{define "sadrzaj"}}
|
||||
|
||||
<!-- lista artikala kao JSON — sadrži id, naziv i prodajnu cenu -->
|
||||
<script>var _ntechArtikli = {{.ArtikliJSON}};</script>
|
||||
<script>
|
||||
var _ntechArtikli = {{.ArtikliJSON }};
|
||||
</script>
|
||||
|
||||
<div style="width:100%;"
|
||||
<div
|
||||
style="width: 100%"
|
||||
x-data="{
|
||||
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
|
||||
artikliOpcije: _ntechArtikli,
|
||||
@@ -66,116 +59,264 @@
|
||||
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2);
|
||||
}
|
||||
}">
|
||||
|
||||
<!-- nazad dugme -->
|
||||
<a href="/prodaja" class="nazad-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true">
|
||||
<polyline points="15 18 9 12 15 6" />
|
||||
</svg>
|
||||
Nazad na prodaju
|
||||
</a>
|
||||
|
||||
<form method="POST" action="/prodaja/nova">
|
||||
|
||||
{{if .Greska}}
|
||||
<div class="poruka-greska greska-animacija">{{.Greska}}</div>
|
||||
{{end}}
|
||||
|
||||
<!-- zaglavlje prodaje -->
|
||||
<div class="kartica forma-kartica animiraj" style="margin-bottom:16px;">
|
||||
<div style="margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);">
|
||||
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">Nova prodaja</span>
|
||||
<div class="kartica forma-kartica animiraj" style="margin-bottom: 16px">
|
||||
<div
|
||||
style="
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 0.5px solid var(--ivica);
|
||||
">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 500; color: var(--tekst-glavni)">Nova prodaja</span>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 14px">
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Klijent</label>
|
||||
<select name="klijent_id" style="width:100%;">
|
||||
<label
|
||||
style="
|
||||
font-size: 13px;
|
||||
color: var(--tekst-sporedni);
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
">Klijent</label>
|
||||
<select name="klijent_id" style="width: 100%">
|
||||
<option value="">— bez klijenta —</option>
|
||||
{{range .Klijenti}}
|
||||
<option value="{{.ID}}">
|
||||
{{if .NazivFirme}}{{.NazivFirme}}{{else}}{{.Ime}} {{.Prezime}}{{end}}
|
||||
{{if .NazivFirme}}{{.NazivFirme}}{{else}}{{.Ime}}
|
||||
{{.Prezime}}{{end}}
|
||||
</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Napomena</label>
|
||||
<textarea name="napomena" rows="2"
|
||||
<label
|
||||
style="
|
||||
font-size: 13px;
|
||||
color: var(--tekst-sporedni);
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
">Napomena</label>
|
||||
<textarea
|
||||
name="napomena"
|
||||
rows="2"
|
||||
placeholder="Interna napomena o prodaji..."
|
||||
style="width:100%;resize:vertical;"></textarea>
|
||||
style="width: 100%; resize: vertical"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- stavke -->
|
||||
<div class="kartica forma-kartica animiraj" style="margin-bottom:16px;">
|
||||
<div style="margin-bottom:16px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);">Stavke</span>
|
||||
<button type="button" @click="dodajStavku()" class="btn-primarno" style="font-size:13px;padding:6px 14px;">
|
||||
<div class="kartica forma-kartica animiraj" style="margin-bottom: 16px">
|
||||
<div
|
||||
style="
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 0.5px solid var(--ivica);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
">
|
||||
<span
|
||||
style="font-size: 16px; font-weight: 500; color: var(--tekst-glavni)">Stavke</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="dodajStavku()"
|
||||
class="btn-primarno"
|
||||
style="font-size: 13px; padding: 6px 14px">
|
||||
+ Dodaj stavku
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- desktop tabela stavki -->
|
||||
<div class="stavke-tabela-wrapper" style="overflow-x:auto;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<div class="stavke-tabela-wrapper" style="overflow-x: auto">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<thead>
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<th style="padding:8px 10px;text-align:left;font-size:12px;font-weight:500;color:var(--tekst-sporedni);">Artikal</th>
|
||||
<th style="padding:8px 10px;text-align:center;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:90px;">Količina</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:140px;">Cena/kom (din)</th>
|
||||
<th style="padding:8px 10px;text-align:right;font-size:12px;font-weight:500;color:var(--tekst-sporedni);width:110px;">Ukupno</th>
|
||||
<th style="width:40px;"></th>
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<th
|
||||
style="
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
">
|
||||
Artikal
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 8px 10px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 90px;
|
||||
">
|
||||
Količina
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 8px 10px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 140px;
|
||||
">
|
||||
Cena/kom (din)
|
||||
</th>
|
||||
<th
|
||||
style="
|
||||
padding: 8px 10px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
width: 110px;
|
||||
">
|
||||
Ukupno
|
||||
</th>
|
||||
<th style="width: 40px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(stavka, i) in stavke" :key="i">
|
||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||
<td style="padding:8px 10px;">
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
<tr style="border-bottom: 0.5px solid var(--ivica)">
|
||||
<td style="padding: 8px 10px">
|
||||
<select
|
||||
:name="'artikal_id[]'"
|
||||
x-model="stavka.artikal_id"
|
||||
@change="popuniCenu(stavka)"
|
||||
:disabled="isMobile"
|
||||
style="width:100%;">
|
||||
style="width: 100%">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
</template>
|
||||
</select>
|
||||
</td>
|
||||
<td style="padding:8px 10px;">
|
||||
<input type="number" :name="'kolicina[]'" x-model="stavka.kolicina"
|
||||
min="1" :disabled="isMobile"
|
||||
<td style="padding: 8px 10px">
|
||||
<input
|
||||
type="number"
|
||||
:name="'kolicina[]'"
|
||||
x-model="stavka.kolicina"
|
||||
min="1"
|
||||
:disabled="isMobile"
|
||||
:style="prekoracenje(i) ? 'border-color:#dc2626;' : ''"
|
||||
style="width:100%;text-align:center;">
|
||||
<div x-show="stavka.artikal_id"
|
||||
style="width: 100%; text-align: center" />
|
||||
<div
|
||||
x-show="stavka.artikal_id"
|
||||
:style="prekoracenje(i) ? 'color:#dc2626' : 'color:var(--tekst-sporedni)'"
|
||||
x-text="'Na stanju: ' + dostupnaKolicina(i)"
|
||||
style="font-size:11px;margin-top:3px;text-align:center;white-space:nowrap;"></div>
|
||||
<div x-show="prekoracenje(i)"
|
||||
style="font-size:11px;color:#dc2626;margin-top:1px;text-align:center;white-space:nowrap;">
|
||||
style="
|
||||
font-size: 11px;
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
"></div>
|
||||
<div
|
||||
x-show="prekoracenje(i)"
|
||||
style="
|
||||
font-size: 11px;
|
||||
color: #dc2626;
|
||||
margin-top: 1px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
">
|
||||
Prekoračena količina
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:8px 10px;">
|
||||
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena"
|
||||
min="0" step="0.01" :disabled="isMobile" style="width:100%;text-align:right;">
|
||||
<td style="padding: 8px 10px">
|
||||
<input
|
||||
type="number"
|
||||
:name="'cena_po_komadu[]'"
|
||||
x-model="stavka.cena"
|
||||
min="0"
|
||||
step="0.01"
|
||||
:disabled="isMobile"
|
||||
style="width: 100%; text-align: right" />
|
||||
</td>
|
||||
<td style="padding:8px 10px;text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
<td
|
||||
style="
|
||||
padding: 8px 10px;
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
<span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
</td>
|
||||
<td style="padding:8px 10px;text-align:center;">
|
||||
<button type="button" @click="ukloniStavku(i)"
|
||||
<td style="padding: 8px 10px; text-align: center">
|
||||
<button
|
||||
type="button"
|
||||
@click="ukloniStavku(i)"
|
||||
x-show="stavke.length > 1"
|
||||
style="background:none;border:none;cursor:pointer;color:#dc2626;font-size:18px;line-height:1;padding:2px 6px;border-radius:4px;transition:background 0.15s;"
|
||||
onmouseover="this.style.background='rgba(220,38,38,0.08)'"
|
||||
onmouseout="this.style.background='none'"
|
||||
title="Ukloni stavku">×</button>
|
||||
style="
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #dc2626;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
transition: background 0.15s;
|
||||
"
|
||||
onmouseover="this.style.background = 'rgba(220,38,38,0.08)'"
|
||||
onmouseout="this.style.background = 'none'"
|
||||
title="Ukloni stavku">
|
||||
×
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="border-top:0.5px solid var(--ivica);">
|
||||
<td colspan="3" style="padding:10px;text-align:right;font-size:13px;color:var(--tekst-sporedni);font-weight:500;">Ukupno:</td>
|
||||
<td style="padding:10px;text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);">
|
||||
<tr style="border-top: 0.5px solid var(--ivica)">
|
||||
<td
|
||||
colspan="3"
|
||||
style="
|
||||
padding: 10px;
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
color: var(--tekst-sporedni);
|
||||
font-weight: 500;
|
||||
">
|
||||
Ukupno:
|
||||
</td>
|
||||
<td
|
||||
style="
|
||||
padding: 10px;
|
||||
text-align: right;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
<span x-text="ukupnoSvega() + ' din'"></span>
|
||||
</td>
|
||||
<td></td>
|
||||
@@ -185,71 +326,150 @@
|
||||
</div>
|
||||
|
||||
<!-- mobilne kartice stavki -->
|
||||
<div class="stavke-kartice" style="display:none;flex-direction:column;gap:10px;">
|
||||
<div
|
||||
class="stavke-kartice"
|
||||
style="display: none; flex-direction: column; gap: 10px">
|
||||
<template x-for="(stavka, i) in stavke" :key="i">
|
||||
<div style="border:0.5px solid var(--ivica);border-radius:8px;padding:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<span style="font-size:13px;font-weight:500;color:var(--tekst-sporedni);"
|
||||
<div
|
||||
style="
|
||||
border: 0.5px solid var(--ivica);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
">
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
">
|
||||
<span
|
||||
style="
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-sporedni);
|
||||
"
|
||||
x-text="'Stavka ' + (i + 1)"></span>
|
||||
<button type="button" @click="ukloniStavku(i)"
|
||||
<button
|
||||
type="button"
|
||||
@click="ukloniStavku(i)"
|
||||
x-show="stavke.length > 1"
|
||||
style="background:none;border:0.5px solid #dc2626;color:#dc2626;cursor:pointer;font-size:13px;padding:2px 8px;border-radius:4px;">
|
||||
style="
|
||||
background: none;
|
||||
border: 0.5px solid #dc2626;
|
||||
color: #dc2626;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
">
|
||||
Ukloni
|
||||
</button>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||||
<div style="display: flex; flex-direction: column; gap: 10px">
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Artikal</label>
|
||||
<select :name="'artikal_id[]'" x-model="stavka.artikal_id"
|
||||
<label
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
">Artikal</label>
|
||||
<select
|
||||
:name="'artikal_id[]'"
|
||||
x-model="stavka.artikal_id"
|
||||
@change="popuniCenu(stavka)"
|
||||
:disabled="!isMobile"
|
||||
style="width:100%;">
|
||||
style="width: 100%">
|
||||
<option value="">— odaberi artikal —</option>
|
||||
<template x-for="a in artikliOpcije" :key="a.id">
|
||||
<option :value="a.id" x-text="a.naziv"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||||
<div
|
||||
style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px">
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Količina</label>
|
||||
<input type="number" :name="'kolicina[]'" x-model="stavka.kolicina" min="1" :disabled="!isMobile"
|
||||
<label
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
">Količina</label>
|
||||
<input
|
||||
type="number"
|
||||
:name="'kolicina[]'"
|
||||
x-model="stavka.kolicina"
|
||||
min="1"
|
||||
:disabled="!isMobile"
|
||||
:style="prekoracenje(i) ? 'border-color:#dc2626;' : ''"
|
||||
style="width:100%;">
|
||||
<div x-show="stavka.artikal_id"
|
||||
style="width: 100%" />
|
||||
<div
|
||||
x-show="stavka.artikal_id"
|
||||
:style="prekoracenje(i) ? 'color:#dc2626' : 'color:var(--tekst-sporedni)'"
|
||||
x-text="'Na stanju: ' + dostupnaKolicina(i)"
|
||||
style="font-size:11px;margin-top:3px;"></div>
|
||||
<div x-show="prekoracenje(i)" style="font-size:11px;color:#dc2626;margin-top:1px;">Prekoračena količina</div>
|
||||
style="font-size: 11px; margin-top: 3px"></div>
|
||||
<div
|
||||
x-show="prekoracenje(i)"
|
||||
style="font-size: 11px; color: #dc2626; margin-top: 1px">
|
||||
Prekoračena količina
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:12px;color:var(--tekst-sporedni);display:block;margin-bottom:4px;">Cena/kom (din)</label>
|
||||
<input type="number" :name="'cena_po_komadu[]'" x-model="stavka.cena" min="0" step="0.01" :disabled="!isMobile" style="width:100%;">
|
||||
<label
|
||||
style="
|
||||
font-size: 12px;
|
||||
color: var(--tekst-sporedni);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
">Cena/kom (din)</label>
|
||||
<input
|
||||
type="number"
|
||||
:name="'cena_po_komadu[]'"
|
||||
x-model="stavka.cena"
|
||||
min="0"
|
||||
step="0.01"
|
||||
:disabled="!isMobile"
|
||||
style="width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:right;font-size:14px;font-weight:500;color:var(--tekst-glavni);">
|
||||
<div
|
||||
style="
|
||||
text-align: right;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--tekst-glavni);
|
||||
">
|
||||
Ukupno: <span x-text="ukupnoStavke(stavka) + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div style="text-align:right;font-size:15px;font-weight:600;color:var(--tekst-glavni);padding:8px 4px;">
|
||||
<div
|
||||
style="
|
||||
text-align: right;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--tekst-glavni);
|
||||
padding: 8px 4px;
|
||||
">
|
||||
Ukupno: <span x-text="ukupnoSvega() + ' din'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- dugmad forme -->
|
||||
<div style="display:flex;justify-content:flex-end;gap:10px;">
|
||||
<div style="display: flex; justify-content: flex-end; gap: 10px">
|
||||
<a href="/prodaja" class="btn-sekundarno">Odustani</a>
|
||||
<button type="submit" class="btn-primarno"
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-primarno"
|
||||
:disabled="imaPrekoracenja()"
|
||||
:style="imaPrekoracenja() ? 'opacity:0.4;cursor:not-allowed;' : ''">
|
||||
Sačuvaj prodaju
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -1,202 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="sr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Priznanica {{.Nalog.BrojNaloga}}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
background: #f5f5f5;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
|
||||
.stranica {
|
||||
background: #fff;
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 8px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.zaglavlje {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.firma-naziv {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.firma-podnazlov {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.firma-kontakt {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.nalog-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.nalog-broj {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
font-family: monospace;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.nalog-datum {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.meta-stavka label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #9ca3af;
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.meta-stavka span {
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
border-bottom: 1.5px solid #111;
|
||||
}
|
||||
|
||||
thead th {
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #6b7280;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; color: #111; background: #f5f5f5; padding: 32px 16px; }
|
||||
.stranica { background: #fff; max-width: 640px; margin: 0 auto; padding: 40px; border-radius: 8px; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08); }
|
||||
.zaglavlje { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid #e5e7eb; }
|
||||
.firma-naziv { font-size: 20px; font-weight: 600; color: #111; }
|
||||
.firma-podnazlov { font-size: 13px; color: #6b7280; margin-top: 3px; }
|
||||
.firma-kontakt { font-size: 12px; color: #6b7280; margin-top: 2px; }
|
||||
.nalog-info { text-align: right; }
|
||||
.nalog-broj { font-size: 16px; font-weight: 600; font-family: monospace; color: #111; }
|
||||
.nalog-datum { font-size: 13px; color: #6b7280; margin-top: 3px; }
|
||||
.meta { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 28px; }
|
||||
.meta-stavka label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: #9ca3af; display: block; margin-bottom: 3px; }
|
||||
.meta-stavka span { font-size: 14px; color: #111; }
|
||||
table { width: 100%; border-collapse: collapse; margin-bottom: 0; }
|
||||
thead tr { border-bottom: 1.5px solid #111; }
|
||||
thead th { padding: 8px 10px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; text-align: left; }
|
||||
thead th:not(:first-child) { text-align: right; }
|
||||
|
||||
tbody tr {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 10px 10px;
|
||||
font-size: 14px;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
tbody td:not(:first-child) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
tfoot tr {
|
||||
border-top: 1.5px solid #111;
|
||||
}
|
||||
|
||||
tfoot td {
|
||||
padding: 12px 10px;
|
||||
}
|
||||
|
||||
.ukupno-label {
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ukupno-iznos {
|
||||
text-align: right;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.ekran-dugmad {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 9px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primarni {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-sekundarni {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body { background: #fff; padding: 0; }
|
||||
.stranica { box-shadow: none; border-radius: 0; padding: 20px; max-width: 100%; }
|
||||
.ekran-dugmad { display: none; }
|
||||
}
|
||||
tbody tr { border-bottom: 1px solid #e5e7eb; }
|
||||
tbody td { padding: 10px 10px; font-size: 14px; color: #111; }
|
||||
tbody td:not(:first-child) { text-align: right; }
|
||||
tfoot tr { border-top: 1.5px solid #111; }
|
||||
tfoot td { padding: 12px 10px; }
|
||||
.ukupno-label { text-align: right; font-size: 13px; font-weight: 500; color: #6b7280; }
|
||||
.ukupno-iznos { text-align: right; font-size: 18px; font-weight: 700; color: #111; }
|
||||
.ekran-dugmad { display: flex; gap: 10px; justify-content: flex-end; margin-top: 28px; }
|
||||
.btn { padding: 9px 20px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; text-decoration: none; display: inline-block; }
|
||||
.btn-primarni { background: #2563eb; color: #fff; }
|
||||
.btn-sekundarni { background: #f3f4f6; color: #374151; }
|
||||
@media print { body { background: #fff; padding: 0; } .stranica { box-shadow: none; border-radius: 0; padding: 20px; max-width: 100%; } .ekran-dugmad { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stranica">
|
||||
|
||||
<div class="zaglavlje">
|
||||
<div>
|
||||
<div class="firma-naziv">{{.NazivFirme}}</div>
|
||||
{{if .Podnazlov}}<div class="firma-podnazlov">{{.Podnazlov}}</div>{{end}}
|
||||
{{if .Adresa}}<div class="firma-kontakt">{{.Adresa}}</div>{{end}}
|
||||
{{if .Telefon}}<div class="firma-kontakt">{{.Telefon}}</div>{{end}}
|
||||
{{if .PIB}}<div class="firma-kontakt">PIB: {{.PIB}}</div>{{end}}
|
||||
{{if .Podnazlov}}
|
||||
<div class="firma-podnazlov">{{.Podnazlov}}</div>
|
||||
{{end}} {{if .Adresa}}
|
||||
<div class="firma-kontakt">{{.Adresa}}</div>
|
||||
{{end}} {{if .Telefon}}
|
||||
<div class="firma-kontakt">{{.Telefon}}</div>
|
||||
{{end}} {{if .PIB}}
|
||||
<div class="firma-kontakt">PIB: {{.PIB}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="nalog-info">
|
||||
<div class="nalog-broj">{{.Nalog.BrojNaloga}}</div>
|
||||
<div class="nalog-datum">{{.Nalog.Datum.Format "02.01.2006. u 15:04"}}</div>
|
||||
<div class="nalog-datum">
|
||||
{{.Nalog.Datum.Format "02.01.2006. u 15:04"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -237,19 +96,41 @@
|
||||
</table>
|
||||
|
||||
{{if .Nalog.Napomena}}
|
||||
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #e5e7eb;">
|
||||
<div style="font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:0.05em;color:#9ca3af;margin-bottom:4px;">Napomena</div>
|
||||
<div style="font-size:13px;color:#374151;">{{.Nalog.Napomena}}</div>
|
||||
<div
|
||||
style="
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
">
|
||||
<div
|
||||
style="
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 4px;
|
||||
">
|
||||
Napomena
|
||||
</div>
|
||||
<div style="font-size: 13px; color: #374151">{{.Nalog.Napomena}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="ekran-dugmad">
|
||||
<button class="btn btn-sekundarni" onclick="window.close()">Zatvori</button>
|
||||
<button class="btn btn-primarni" onclick="window.print()">Štampaj</button>
|
||||
<button class="btn btn-sekundarni" onclick="window.close()">
|
||||
Zatvori
|
||||
</button>
|
||||
<button class="btn btn-primarni" onclick="window.print()">
|
||||
Štampaj
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>window.onload = function() { window.print(); };</script>
|
||||
</body>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
window.print();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,26 +17,13 @@
|
||||
.servis-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
|
||||
.servis-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
|
||||
|
||||
.servis-kartice {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.servis-kartice { display: none; flex-direction: column; gap: 12px; }
|
||||
.servis-kartica:nth-child(1) { animation-delay: 0.04s; }
|
||||
.servis-kartica:nth-child(2) { animation-delay: 0.10s; }
|
||||
.servis-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||
.servis-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||
.servis-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.status-badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 500; white-space: nowrap; }
|
||||
|
||||
.status-primljeno { background: rgba(148,163,184,0.15); color: #94a3b8; }
|
||||
.status-dijagnostika { background: rgba(59,130,246,0.15); color: #3b82f6; }
|
||||
|
||||
@@ -9,18 +9,8 @@
|
||||
.detalji-kartica:nth-child(3) { animation-delay: 0.20s; }
|
||||
.detalji-kartica:nth-child(4) { animation-delay: 0.28s; }
|
||||
.detalji-kartica:nth-child(5) { animation-delay: 0.36s; }
|
||||
|
||||
.poruka-animacija { animation: slideDown 0.3s ease forwards; }
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 500; white-space: nowrap; }
|
||||
.status-primljeno { background: rgba(148,163,184,0.15); color: #94a3b8; }
|
||||
.status-dijagnostika { background: rgba(59,130,246,0.15); color: #3b82f6; }
|
||||
.status-ceka { background: rgba(249,115,22,0.15); color: #f97316; }
|
||||
|
||||
@@ -6,74 +6,20 @@
|
||||
<title>Dvostepena verifikacija — NTech</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: #0f1117;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
.kartica {
|
||||
background: #1a1d27;
|
||||
border: 0.5px solid #2d3148;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f1117; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 16px; }
|
||||
.kartica { background: #1a1d27; border: 0.5px solid #2d3148; border-radius: 16px; padding: 40px; width: 100%; max-width: 380px; }
|
||||
.logo { text-align: center; margin-bottom: 32px; }
|
||||
.logo-naziv { font-size: 22px; font-weight: 600; color: #fff; }
|
||||
.opis { font-size: 13px; color: #9ca3af; margin-bottom: 24px; line-height: 1.5; }
|
||||
h1 { font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 8px; }
|
||||
.polje { margin-bottom: 16px; }
|
||||
label { display: block; font-size: 13px; color: #9ca3af; margin-bottom: 6px; }
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: #0f1117;
|
||||
border: 0.5px solid #2d3148;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
letter-spacing: 6px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
input { width: 100%; padding: 8px 12px; background: #0f1117; border: 0.5px solid #2d3148; border-radius: 8px; font-size: 20px; color: #fff; outline: none; text-align: center; letter-spacing: 6px; transition: border-color 0.2s; }
|
||||
input:focus { border-color: #e53e3e; }
|
||||
.dugme {
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
background: #e53e3e;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.dugme { width: 100%; padding: 11px; background: #e53e3e; color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; margin-top: 8px; transition: opacity 0.2s; }
|
||||
.dugme:hover { opacity: 0.88; }
|
||||
.greska {
|
||||
background: #fef2f2;
|
||||
border: 0.5px solid #fca5a5;
|
||||
color: #dc2626;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nazad {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
text-decoration: none;
|
||||
}
|
||||
.greska { background: #fef2f2; border: 0.5px solid #fca5a5; color: #dc2626; border-radius: 8px; padding: 10px 14px; font-size: 13px; margin-bottom: 20px; }
|
||||
.nazad { display: block; text-align: center; margin-top: 16px; font-size: 13px; color: #6b7280; text-decoration: none; }
|
||||
.nazad:hover { color: #9ca3af; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -38,91 +38,21 @@
|
||||
|
||||
{{if .AppPozadina}}
|
||||
<style>
|
||||
.app-bg {
|
||||
position: fixed;
|
||||
inset: {{if ne .AppPozadinaBlurPozadine "0"}}-20px{{else}}0{{end}};
|
||||
background-image: url('{{.AppPozadina}}');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: blur({{.AppPozadinaBlurPozadine}}px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.app-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,{{.AppPozadinaOpacity}}%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
.raspored {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.sidebar {
|
||||
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;
|
||||
}
|
||||
.sidebar .nav-stavka,
|
||||
.sidebar .logo-naziv,
|
||||
.sidebar .logo-podnazlov {
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||
color: rgba(255,255,255,0.95) !important;
|
||||
}
|
||||
.sidebar .nav-stavka svg {
|
||||
color: rgba(255,255,255,0.95) !important;
|
||||
stroke: rgba(255,255,255,0.95) !important;
|
||||
}
|
||||
.sidebar .nav-oznaka {
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||
color: rgba(255,255,255,0.7) !important;
|
||||
}
|
||||
.topbar {
|
||||
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(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;
|
||||
}
|
||||
.kartica p,
|
||||
.kartica span,
|
||||
.kartica h1,
|
||||
.kartica h2,
|
||||
.kartica h3,
|
||||
.kartica h4,
|
||||
.kartica label,
|
||||
.kartica td,
|
||||
.kartica th,
|
||||
.kartica li,
|
||||
.kartica a {
|
||||
color: rgba(255,255,255,0.95) !important;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.7);
|
||||
}
|
||||
table, th, td {
|
||||
color: rgba(255,255,255,0.95) !important;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.8);
|
||||
}
|
||||
tr {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
tr:hover {
|
||||
background: rgba(0,0,0,0.35);
|
||||
}
|
||||
thead th {
|
||||
background: rgba(0,0,0,0.4) !important;
|
||||
}
|
||||
div:has(> canvas) {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.app-bg { position: fixed; inset: {{if ne .AppPozadinaBlurPozadine "0"}}-20px{{else}}0{{end}}; background-image: url('{{.AppPozadina}}'); background-size: cover; background-position: center; filter: blur({{.AppPozadinaBlurPozadine}}px); pointer-events: none; z-index: 0; }
|
||||
.app-overlay { position: fixed; inset: 0; background: rgba(0,0,0,{{.AppPozadinaOpacity}}%); pointer-events: none; z-index: 1; }
|
||||
.raspored { position: relative; z-index: 2; }
|
||||
.sidebar { 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; }
|
||||
.sidebar .nav-stavka, .sidebar .logo-naziv, .sidebar .logo-podnazlov { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.95) !important; }
|
||||
.sidebar .nav-stavka svg { color: rgba(255,255,255,0.95) !important; stroke: rgba(255,255,255,0.95) !important; }
|
||||
.sidebar .nav-oznaka { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.7) !important; }
|
||||
.topbar { 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(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; }
|
||||
.kartica p, .kartica span, .kartica h1, .kartica h2, .kartica h3, .kartica h4, .kartica label, .kartica td, .kartica th, .kartica li, .kartica a { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.7); }
|
||||
table, th, td { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.8); }
|
||||
tr { background: rgba(0,0,0,0.2); }
|
||||
tr:hover { background: rgba(0,0,0,0.35); }
|
||||
thead th { background: rgba(0,0,0,0.4) !important; }
|
||||
div:has(> canvas) { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 8px; }
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
Reference in New Issue
Block a user