Servis forma, animacije, hover efekti i pojačane senke

Servis:
- Nova polja: ostecenja, pin_uredjaja, pribor (migracija 051)
- Default garancija iz podešavanja, svič "Bez garancije" u formi
- Podešavanja → Servis: konfigurabilan rok garancije (migracija 052)
- Default datum prijema = danas; datum_prijema se eksplicitno upisuje
- Sidebar link za Servis podešavanja

PDV/Nivelacije:
- Default raspon datuma = početak/kraj tekućeg meseca (KIR, KPR, Nivelacije)
- Dodata class="tabela" na tabele bez klase (KIR, KPR, Obračun, Nivelacije)

Animacije (Moj profil → Tema):
- Korisnik bira vrstu animacije: bez, fadeInUp, fadeIn, scaleIn, slideLeft
- Čuva se po korisniku u korisnici.lokalna_animacija (migracija 053)
- CSS [data-animacija] radi na body (globalno) i na preview wrapperima (izolovano)
- Preview animacije izolovan: data-animacija na #anim-preview-wrap, ne na body
- Mobilne kartice se animiraju kad korisnik odabere stil (podrazumevano ne)
- Animacija primenjena direktno na .tabela tbody tr (bez potrebe za .animiraj)

Hover efekti (Moj profil → Tema):
- Opcije: podrazumevano, bez, podizanje, svetlost, zoom, boja
- Čuva se po korisniku u korisnici.lokalni_hover (migracija 054)
- CSS [data-hover] radi izolovano; preview menja samo #hover-preview-wrap
- Pojačane senke u oba teme (--senka i nova --senka-hover promenljiva)
- Transition dodat za transform i background na karticama

Grafikon (Izveštaji): toggle zamenjen globalnim .toggl/.toggl-klizac svičom
This commit is contained in:
2026-06-18 02:21:06 +02:00
parent b417ff6d02
commit f29e76612e
36 changed files with 707 additions and 53 deletions
+3
View File
@@ -247,6 +247,7 @@ func main() {
r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste) r.Get("/admin/podesavanja/opste", h.PodesavanjaOpste)
r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled) r.Get("/admin/podesavanja/izgled", h.PodesavanjaIzgled)
r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem) r.Get("/admin/podesavanja/sistem", h.PodesavanjaSistem)
r.Get("/admin/podesavanja/servis", h.PodesavanjaServis)
r.Get("/admin/podesavanja/kalkulacija-pdv", h.PdvStope) r.Get("/admin/podesavanja/kalkulacija-pdv", h.PdvStope)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu) r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/dodaj", h.DodajPdvStopu)
r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu) r.With(doz("podesavanja.izmeni")).Post("/podesavanja/pdv-stope/{id}/izmeni", h.IzmeniPdvStopu)
@@ -356,6 +357,8 @@ func main() {
r.Post("/admin/profil/totp/deaktiviraj", h.AdminTotpDeaktivacija) r.Post("/admin/profil/totp/deaktiviraj", h.AdminTotpDeaktivacija)
r.Post("/admin/profil/totp/kodovi", h.AdminTotpRegenerisiKodove) r.Post("/admin/profil/totp/kodovi", h.AdminTotpRegenerisiKodove)
r.With(doz("tema.lokalno")).Post("/profil/tema", h.SacuvajLokalnuTemu) r.With(doz("tema.lokalno")).Post("/profil/tema", h.SacuvajLokalnuTemu)
r.With(doz("tema.lokalno")).Post("/profil/animacija", h.SacuvajLokalnuAnimaciju)
r.With(doz("tema.lokalno")).Post("/profil/hover", h.SacuvajLokalniHover)
r.Get("/profil/tema", h.ProfilTema) r.Get("/profil/tema", h.ProfilTema)
r.With(doz("tema.lokalno")).Post("/profil/pozadina", h.ProfilOtpremiPozadinu) r.With(doz("tema.lokalno")).Post("/profil/pozadina", h.ProfilOtpremiPozadinu)
r.With(doz("tema.lokalno")).Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu) r.With(doz("tema.lokalno")).Post("/profil/pozadina/ukloni", h.ProfilUkloniPozadinu)
+15 -1
View File
@@ -12,12 +12,26 @@ require (
) )
require ( require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.1.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/f-amaral/go-async v0.3.0 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/johnfercher/go-tree v1.1.0 // indirect
github.com/johnfercher/maroto/v2 v2.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pdfcpu/pdfcpu v0.11.1 // indirect
github.com/phpdave11/gofpdf v1.4.3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/image v0.37.0 // indirect
golang.org/x/sys v0.45.0 // indirect golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/libc v1.72.3 // indirect modernc.org/libc v1.72.3 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
+41
View File
@@ -1,9 +1,17 @@
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/f-amaral/go-async v0.3.0 h1:h4kLsX7aKfdWaHvV0lf+/EE3OIeCzyeDYJDb/vDZUyg=
github.com/f-amaral/go-async v0.3.0/go.mod h1:Hz5Qr6DAWpbTTUjytnrg1WIsDgS7NtOei5y8SipYS7U=
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
@@ -12,23 +20,50 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/johnfercher/go-tree v1.1.0 h1:L0Fs5jLR1uA2e/CwfHjNdO/Lt4IGQ46QgxarAC1yeXs=
github.com/johnfercher/go-tree v1.1.0/go.mod h1:DUO6QkXIFh1K7jeGBIkLCZaeUgnkdQAsB64FDSoHswg=
github.com/johnfercher/maroto/v2 v2.4.0 h1:Nc/jA2RCZvNZESrQj41HJOgtkwmerSHd5FUbP4dRrIE=
github.com/johnfercher/maroto/v2 v2.4.0/go.mod h1:Nnxa3g4f+vzdx/u/dUgx/52HnrCOCt5QBPSdeSlkFZQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pdfcpu/pdfcpu v0.11.1 h1:htHBSkGH5jMKWC6e0sihBFbcKZ8vG1M67c8/dJxhjas=
github.com/pdfcpu/pdfcpu v0.11.1/go.mod h1:pP3aGga7pRvwFWAm9WwFvo+V68DfANi9kxSQYioNYcw=
github.com/phpdave11/gofpdf v1.4.3 h1:M/zHvS8FO3zh9tUd2RCOPEjyuVcs281FCyF22Qlz/IA=
github.com/phpdave11/gofpdf v1.4.3/go.mod h1:MAwzoUIgD3J55u0rxIG2eu37c+XWhBtXSpPAhnQXf/o=
github.com/phpdave11/gofpdi v1.0.15/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA=
golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -36,8 +71,14 @@ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
+2
View File
@@ -149,6 +149,8 @@ type KorisniciRepository interface {
SacuvajTotpTajnu(ctx context.Context, id int64, tajna string) error SacuvajTotpTajnu(ctx context.Context, id int64, tajna string) error
SacuvajLokalnuTemu(ctx context.Context, id int64, lokalnaTema string, koristi bool) error SacuvajLokalnuTemu(ctx context.Context, id int64, lokalnaTema string, koristi bool) error
SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine, glassOpacity string) error SacuvajLokalnuPozadinu(ctx context.Context, id int64, pozadina, opacity, blur, blurPozadine, glassOpacity string) error
SacuvajLokalnuAnimaciju(ctx context.Context, id int64, animacija string) error
SacuvajLokalniHover(ctx context.Context, id int64, hover string) error
SacuvajAvatar(ctx context.Context, id int64, putanja string) error SacuvajAvatar(ctx context.Context, id int64, putanja string) error
PostojiIjedan(ctx context.Context) (bool, error) PostojiIjedan(ctx context.Context) (bool, error)
Obrisi(ctx context.Context, id int64) error Obrisi(ctx context.Context, id int64) error
+38 -3
View File
@@ -30,6 +30,8 @@ type korisnikOpcije struct {
lokalnaPozadinaBlurPozadine sql.NullString lokalnaPozadinaBlurPozadine sql.NullString
lokalnaPozadinaGlassOpacity sql.NullString lokalnaPozadinaGlassOpacity sql.NullString
avatarPutanja sql.NullString avatarPutanja sql.NullString
lokalnaAnimacija sql.NullString
lokalniHover sql.NullString
} }
// dodeliOpcijeKorisnika prenosi vrednosti iz korisnikOpcije na model.Korisnik // dodeliOpcijeKorisnika prenosi vrednosti iz korisnikOpcije na model.Korisnik
@@ -44,6 +46,8 @@ func dodeliOpcijeKorisnika(k *model.Korisnik, o korisnikOpcije) {
k.LokalnaPozadinaBlurPozadine = o.lokalnaPozadinaBlurPozadine.String k.LokalnaPozadinaBlurPozadine = o.lokalnaPozadinaBlurPozadine.String
k.LokalnaPozadinaGlassOpacity = o.lokalnaPozadinaGlassOpacity.String k.LokalnaPozadinaGlassOpacity = o.lokalnaPozadinaGlassOpacity.String
k.AvatarPutanja = o.avatarPutanja.String k.AvatarPutanja = o.avatarPutanja.String
k.LokalnaAnimacija = o.lokalnaAnimacija.String
k.LokalniHover = o.lokalniHover.String
} }
// skeniraiKorisnika čita jedan red iz baze i popunjava model.Korisnik // skeniraiKorisnika čita jedan red iz baze i popunjava model.Korisnik
@@ -55,6 +59,7 @@ func skeniraiKorisnika(row interface{ Scan(...any) error }) (*model.Korisnik, er
&o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja, &o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja,
&o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur, &o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur,
&o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja, &o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja,
&o.lokalnaAnimacija, &o.lokalniHover,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -98,7 +103,8 @@ func (r *sqliteKorisniciRepo) DohvatiPoImenu(ctx context.Context, korisnickoIme
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja, COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'), COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'), COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, '') COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, ''),
COALESCE(lokalna_animacija, ''), COALESCE(lokalni_hover, '')
FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme) FROM korisnici WHERE korisnicko_ime = ?`, korisnickoIme)
k, err := skeniraiKorisnika(row) k, err := skeniraiKorisnika(row)
if err != nil { if err != nil {
@@ -114,7 +120,8 @@ func (r *sqliteKorisniciRepo) DohvatiPoID(ctx context.Context, id int64) (*model
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja, COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'), COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'), COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, '') COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, ''),
COALESCE(lokalna_animacija, ''), COALESCE(lokalni_hover, '')
FROM korisnici WHERE id = ?`, id) FROM korisnici WHERE id = ?`, id)
k, err := skeniraiKorisnika(row) k, err := skeniraiKorisnika(row)
if err != nil { if err != nil {
@@ -130,7 +137,8 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja, COALESCE(lokalna_tema, ''), koristi_lokalnu_temu, datum_kreiranja,
COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'), COALESCE(lokalna_pozadina, ''), COALESCE(lokalna_pozadina_opacity, '50'),
COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'), COALESCE(lokalna_pozadina_blur, '12'), COALESCE(lokalna_pozadina_blur_pozadine, '0'),
COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, '') COALESCE(lokalna_pozadina_glass_opacity, '10'), COALESCE(avatar_putanja, ''),
COALESCE(lokalna_animacija, ''), COALESCE(lokalni_hover, '')
FROM korisnici ORDER BY datum_kreiranja ASC`) FROM korisnici ORDER BY datum_kreiranja ASC`)
if err != nil { if err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err) return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
@@ -145,6 +153,7 @@ func (r *sqliteKorisniciRepo) Lista(ctx context.Context) ([]model.Korisnik, erro
&o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja, &o.lokalnaTema, &o.koristiLokalnuTemu, &o.datumKreiranja,
&o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur, &o.lokalnaPozadina, &o.lokalnaPozadinaOpacity, &o.lokalnaPozadinaBlur,
&o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja, &o.lokalnaPozadinaBlurPozadine, &o.lokalnaPozadinaGlassOpacity, &o.avatarPutanja,
&o.lokalnaAnimacija, &o.lokalniHover,
); err != nil { ); err != nil {
return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err) return nil, fmt.Errorf("ntech: korisnici.Lista: %w", err)
} }
@@ -194,6 +203,32 @@ func (r *sqliteKorisniciRepo) SacuvajLokalnuTemu(ctx context.Context, id int64,
return nil return nil
} }
func (r *sqliteKorisniciRepo) SacuvajLokalnuAnimaciju(ctx context.Context, id int64, animacija string) error {
var val any
if animacija != "" {
val = animacija
}
_, err := r.db.ExecContext(ctx,
`UPDATE korisnici SET lokalna_animacija = ? WHERE id = ?`, val, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.SacuvajLokalnuAnimaciju: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) SacuvajLokalniHover(ctx context.Context, id int64, hover string) error {
var val any
if hover != "" {
val = hover
}
_, err := r.db.ExecContext(ctx,
`UPDATE korisnici SET lokalni_hover = ? WHERE id = ?`, val, id)
if err != nil {
return fmt.Errorf("ntech: korisnici.SacuvajLokalniHover: %w", err)
}
return nil
}
func (r *sqliteKorisniciRepo) AzurirajUlogu(ctx context.Context, id int64, uloga string) error { func (r *sqliteKorisniciRepo) AzurirajUlogu(ctx context.Context, id int64, uloga string) error {
_, err := r.db.ExecContext(ctx, `UPDATE korisnici SET uloga = ? WHERE id = ?`, uloga, id) _, err := r.db.ExecContext(ctx, `UPDATE korisnici SET uloga = ? WHERE id = ?`, uloga, id)
if err != nil { if err != nil {
+16 -5
View File
@@ -43,6 +43,7 @@ func (r *ServisRepo) Lista(ctx context.Context, pretraga, status string) ([]mode
sn.id, sn.klijent_id, sn.tehnicar_id, sn.broj_naloga, sn.uredjaj, sn.serijski_broj, sn.id, sn.klijent_id, sn.tehnicar_id, sn.broj_naloga, sn.uredjaj, sn.serijski_broj,
sn.opis_kvara, sn.status, sn.cena_od, sn.cena_do, sn.cena_konacna, sn.opis_kvara, sn.status, sn.cena_od, sn.cena_do, sn.cena_konacna,
sn.avans, sn.napomena, sn.garancija_do, sn.datum_prijema, sn.datum_zavrsetka, sn.avans, sn.napomena, sn.garancija_do, sn.datum_prijema, sn.datum_zavrsetka,
sn.ostecenja, sn.pin_uredjaja, sn.pribor,
COALESCE(kp.naziv, '') AS klijent_naziv COALESCE(kp.naziv, '') AS klijent_naziv
FROM servisni_nalozi sn FROM servisni_nalozi sn
LEFT JOIN klijent_prikaz kp ON kp.id = sn.klijent_id LEFT JOIN klijent_prikaz kp ON kp.id = sn.klijent_id
@@ -88,7 +89,8 @@ func (r *ServisRepo) DohvatiID(ctx context.Context, id int64) (*model.ServisniNa
SELECT SELECT
id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj,
opis_kvara, status, cena_od, cena_do, cena_konacna, opis_kvara, status, cena_od, cena_do, cena_konacna,
avans, napomena, garancija_do, datum_prijema, datum_zavrsetka avans, napomena, garancija_do, datum_prijema, datum_zavrsetka,
ostecenja, pin_uredjaja, pribor
FROM servisni_nalozi WHERE id = ?`, id) FROM servisni_nalozi WHERE id = ?`, id)
var n model.ServisniNalog var n model.ServisniNalog
@@ -105,13 +107,16 @@ func (r *ServisRepo) Kreiraj(ctx context.Context, n *model.ServisniNalog) (int64
rezultat, err := r.db.ExecContext(ctx, ` rezultat, err := r.db.ExecContext(ctx, `
INSERT INTO servisni_nalozi INSERT INTO servisni_nalozi
(klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, opis_kvara, (klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, opis_kvara,
status, cena_od, cena_do, cena_konacna, avans, napomena, garancija_do, datum_zavrsetka) status, cena_od, cena_do, cena_konacna, avans, napomena, garancija_do, datum_zavrsetka,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ostecenja, pin_uredjaja, pribor, datum_prijema)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
nullInt64(n.KlijentID), nullInt64(n.TehnicarID), n.BrojNaloga, n.Uredjaj, nullInt64(n.KlijentID), nullInt64(n.TehnicarID), n.BrojNaloga, n.Uredjaj,
nullString(n.SerijskiBroj), n.OpisKvara, n.Status, nullString(n.SerijskiBroj), n.OpisKvara, n.Status,
nullFloat64(n.CenaOd), nullFloat64(n.CenaDo), nullFloat64(n.CenaKonacna), nullFloat64(n.CenaOd), nullFloat64(n.CenaDo), nullFloat64(n.CenaKonacna),
nullFloat64(n.Avans), nullString(n.Napomena), nullFloat64(n.Avans), nullString(n.Napomena),
nullTime(n.GarancijaDo), nullTime(n.DatumZavrsetka), nullTime(n.GarancijaDo), nullTime(n.DatumZavrsetka),
nullString(n.Ostecenja), nullString(n.PinUredjaja), nullString(n.Pribor),
n.DatumPrijema,
) )
if err != nil { if err != nil {
return 0, fmt.Errorf("ntech: ServisRepo.Kreiraj: %w", err) return 0, fmt.Errorf("ntech: ServisRepo.Kreiraj: %w", err)
@@ -131,11 +136,13 @@ func (r *ServisRepo) Izmeni(ctx context.Context, n *model.ServisniNalog) error {
UPDATE servisni_nalozi SET UPDATE servisni_nalozi SET
klijent_id = ?, tehnicar_id = ?, uredjaj = ?, serijski_broj = ?, opis_kvara = ?, klijent_id = ?, tehnicar_id = ?, uredjaj = ?, serijski_broj = ?, opis_kvara = ?,
status = ?, cena_od = ?, cena_do = ?, cena_konacna = ?, status = ?, cena_od = ?, cena_do = ?, cena_konacna = ?,
avans = ?, napomena = ?, garancija_do = ?, datum_zavrsetka = ? avans = ?, napomena = ?, garancija_do = ?, datum_zavrsetka = ?,
ostecenja = ?, pin_uredjaja = ?, pribor = ?
WHERE id = ?`, WHERE id = ?`,
nullInt64(n.KlijentID), nullInt64(n.TehnicarID), n.Uredjaj, nullString(n.SerijskiBroj), n.OpisKvara, nullInt64(n.KlijentID), nullInt64(n.TehnicarID), n.Uredjaj, nullString(n.SerijskiBroj), n.OpisKvara,
n.Status, nullFloat64(n.CenaOd), nullFloat64(n.CenaDo), nullFloat64(n.CenaKonacna), n.Status, nullFloat64(n.CenaOd), nullFloat64(n.CenaDo), nullFloat64(n.CenaKonacna),
nullFloat64(n.Avans), nullString(n.Napomena), nullTime(n.GarancijaDo), nullTime(n.DatumZavrsetka), nullFloat64(n.Avans), nullString(n.Napomena), nullTime(n.GarancijaDo), nullTime(n.DatumZavrsetka),
nullString(n.Ostecenja), nullString(n.PinUredjaja), nullString(n.Pribor),
n.ID, n.ID,
) )
if err != nil { if err != nil {
@@ -159,7 +166,7 @@ func (r *ServisRepo) Obrisi(ctx context.Context, id int64) error {
// klijentNaziv je opcioni pokazivač, nil kada se čita bez JOIN-a // klijentNaziv je opcioni pokazivač, nil kada se čita bez JOIN-a
func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *string) error { func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *string) error {
var klijentID, tehnicarID sql.NullInt64 var klijentID, tehnicarID sql.NullInt64
var serijskiBroj, napomena sql.NullString var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor sql.NullString
var cenaOd, cenaDo, cenaKonacna, avans sql.NullFloat64 var cenaOd, cenaDo, cenaKonacna, avans sql.NullFloat64
var garancijaDo, datumZavrsetka sql.NullTime var garancijaDo, datumZavrsetka sql.NullTime
@@ -167,6 +174,7 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
&n.ID, &klijentID, &tehnicarID, &n.BrojNaloga, &n.Uredjaj, &serijskiBroj, &n.ID, &klijentID, &tehnicarID, &n.BrojNaloga, &n.Uredjaj, &serijskiBroj,
&n.OpisKvara, &n.Status, &cenaOd, &cenaDo, &cenaKonacna, &n.OpisKvara, &n.Status, &cenaOd, &cenaDo, &cenaKonacna,
&avans, &napomena, &garancijaDo, &n.DatumPrijema, &datumZavrsetka, &avans, &napomena, &garancijaDo, &n.DatumPrijema, &datumZavrsetka,
&ostecenja, &pinUredjaja, &pribor,
} }
if klijentNaziv != nil { if klijentNaziv != nil {
@@ -187,6 +195,9 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st
} }
n.SerijskiBroj = serijskiBroj.String n.SerijskiBroj = serijskiBroj.String
n.Napomena = napomena.String n.Napomena = napomena.String
n.Ostecenja = ostecenja.String
n.PinUredjaja = pinUredjaja.String
n.Pribor = pribor.String
if cenaOd.Valid { if cenaOd.Valid {
v := cenaOd.Float64 v := cenaOd.Float64
n.CenaOd = &v n.CenaOd = &v
+2
View File
@@ -46,6 +46,8 @@ type podaciProfilTema struct {
LokalnaPozadinaBlur string LokalnaPozadinaBlur string
LokalnaPozadinaBlurPozadine string LokalnaPozadinaBlurPozadine string
LokalnaPozadinaGlassOpacity string LokalnaPozadinaGlassOpacity string
LokalnaAnimacija string
LokalniHover string
} }
// AdminKorisnici prikazuje listu korisnika // AdminKorisnici prikazuje listu korisnika
+4
View File
@@ -211,6 +211,10 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
ps.AppPozadinaGlassOpacity = "10" ps.AppPozadinaGlassOpacity = "10"
} }
} }
if korisnik != nil {
ps.LokalnaAnimacija = korisnik.LokalnaAnimacija
ps.LokalniHover = korisnik.LokalniHover
}
return ps return ps
} }
+1 -1
View File
@@ -24,7 +24,7 @@ var saSidebar = []string{
"klijenti", "klijent_forma", "klijenti", "klijent_forma",
"magacin", "magacin_forma", "magacin", "magacin_forma",
"nabavke", "nabavka_forma", "nabavka_detalji", "nabavke", "nabavka_forma", "nabavka_detalji",
"podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja", "podesavanja_opste", "podesavanja_izgled", "podesavanja_sistem", "podesavanja_servis",
"pdv_stope", "pdv_stope",
"pdv_kir", "pdv_kir_forma", "pdv_kir", "pdv_kir_forma",
"pdv_kpr", "pdv_kpr_forma", "pdv_kpr", "pdv_kpr_forma",
+13
View File
@@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"ntech/internal/db/sqlite" "ntech/internal/db/sqlite"
"ntech/internal/middleware" "ntech/internal/middleware"
@@ -34,6 +35,18 @@ func (h *Handler) Nivelacije(w http.ResponseWriter, r *http.Request) {
odStr := r.URL.Query().Get("od") odStr := r.URL.Query().Get("od")
doStr := r.URL.Query().Get("do") doStr := r.URL.Query().Get("do")
// podrazumevano: tekući mesec (od prvog do poslednjeg dana)
if odStr == "" || doStr == "" {
sada := time.Now()
prvi := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location())
poslednji := prvi.AddDate(0, 1, -1)
if odStr == "" {
odStr = prvi.Format("2006-01-02")
}
if doStr == "" {
doStr = poslednji.Format("2006-01-02")
}
}
zapisi, err := h.NivelacijaRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr)) zapisi, err := h.NivelacijaRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr))
if err != nil { if err != nil {
http.Error(w, "Greška pri učitavanju nivelacija", http.StatusInternalServerError) http.Error(w, "Greška pri učitavanju nivelacija", http.StatusInternalServerError)
+12
View File
@@ -61,6 +61,18 @@ func (h *Handler) PdvKir(w http.ResponseWriter, r *http.Request) {
odStr := r.URL.Query().Get("od") odStr := r.URL.Query().Get("od")
doStr := r.URL.Query().Get("do") doStr := r.URL.Query().Get("do")
// podrazumevano: tekući mesec (od prvog do poslednjeg dana)
if odStr == "" || doStr == "" {
sada := time.Now()
prvi := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location())
poslednji := prvi.AddDate(0, 1, -1)
if odStr == "" {
odStr = prvi.Format("2006-01-02")
}
if doStr == "" {
doStr = poslednji.Format("2006-01-02")
}
}
zapisi, err := h.PdvKirRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr)) zapisi, err := h.PdvKirRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr))
if err != nil { if err != nil {
http.Error(w, "Greška pri učitavanju knjige izdatih računa", http.StatusInternalServerError) http.Error(w, "Greška pri učitavanju knjige izdatih računa", http.StatusInternalServerError)
+12
View File
@@ -42,6 +42,18 @@ func (h *Handler) PdvKpr(w http.ResponseWriter, r *http.Request) {
odStr := r.URL.Query().Get("od") odStr := r.URL.Query().Get("od")
doStr := r.URL.Query().Get("do") doStr := r.URL.Query().Get("do")
// podrazumevano: tekući mesec (od prvog do poslednjeg dana)
if odStr == "" || doStr == "" {
sada := time.Now()
prvi := time.Date(sada.Year(), sada.Month(), 1, 0, 0, 0, 0, sada.Location())
poslednji := prvi.AddDate(0, 1, -1)
if odStr == "" {
odStr = prvi.Format("2006-01-02")
}
if doStr == "" {
doStr = poslednji.Format("2006-01-02")
}
}
zapisi, err := h.PdvKprRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr)) zapisi, err := h.PdvKprRepo.Lista(r.Context(), parsiraDatumOpcionalno(odStr), parsiraDatumOpcionalno(doStr))
if err != nil { if err != nil {
http.Error(w, "Greška pri učitavanju knjige primljenih računa", http.StatusInternalServerError) http.Error(w, "Greška pri učitavanju knjige primljenih računa", http.StatusInternalServerError)
+30
View File
@@ -43,6 +43,7 @@ type PodaciPodesavanja struct {
BackupIntervalSati string BackupIntervalSati string
BackupBrojKopija string BackupBrojKopija string
KalkulacijaMarza string KalkulacijaMarza string
ServisGarancijaMeseci string
LoginPozadina string LoginPozadina string
LoginPozadinaOpacity string LoginPozadinaOpacity string
LoginPozadinaBlurPozadine string LoginPozadinaBlurPozadine string
@@ -314,6 +315,20 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
} }
} }
// podrazumevani rok garancije za servis (meseci, 0120)
if v := strings.TrimSpace(r.FormValue("servis_garancija_meseci")); v != "" {
n, err := strconv.Atoi(v)
if err != nil || n < 0 || n > 120 {
middleware.SetFlash(w, r, h.DB, "greska", "Rok garancije mora biti broj između 0 i 120 meseci.")
http.Redirect(w, r, sledeci, http.StatusSeeOther)
return
}
if err := ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "servis_garancija_meseci", strconv.Itoa(n)); err != nil {
http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError)
return
}
}
http.Redirect(w, r, sledeci+"?sacuvano=1", http.StatusSeeOther) http.Redirect(w, r, sledeci+"?sacuvano=1", http.StatusSeeOther)
} }
@@ -438,6 +453,20 @@ func (h *Handler) UkloniLogo(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/podesavanja/opste?sacuvano=1", http.StatusSeeOther) http.Redirect(w, r, "/admin/podesavanja/opste?sacuvano=1", http.StatusSeeOther)
} }
// PodesavanjaServis renderuje stranicu sa podešavanjima servisnog modula
func (h *Handler) PodesavanjaServis(w http.ResponseWriter, r *http.Request) {
if _, ok := h.zahtevajDozvolu(w, r, "podesavanja.pregled"); !ok {
return
}
podaci, err := h.napuniPodaciPodesavanja(r, "Podešavanja — Servis")
if err != nil {
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
return
}
podaci.Stranica = "podesavanja-servis"
h.renderujTemplate(w, "podesavanja_servis", podaci)
}
// generisiImeUploada vraća slučajno hex ime (16 bajtova) sa datom ekstenzijom // generisiImeUploada vraća slučajno hex ime (16 bajtova) sa datom ekstenzijom
func generisiImeUploada(ext string) (string, error) { func generisiImeUploada(ext string) (string, error) {
buf := make([]byte, 16) buf := make([]byte, 16)
@@ -669,6 +698,7 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
BackupIntervalSati: vrednostIliDefault(podesavanja, "backup_interval_sati", "24"), BackupIntervalSati: vrednostIliDefault(podesavanja, "backup_interval_sati", "24"),
BackupBrojKopija: vrednostIliDefault(podesavanja, "backup_broj_kopija", "7"), BackupBrojKopija: vrednostIliDefault(podesavanja, "backup_broj_kopija", "7"),
KalkulacijaMarza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"), KalkulacijaMarza: vrednostIliDefault(podesavanja, "kalkulacija_marza", "20"),
ServisGarancijaMeseci: vrednostIliDefault(podesavanja, "servis_garancija_meseci", "2"),
}, nil }, nil
} }
+80
View File
@@ -48,6 +48,8 @@ func (h *Handler) ProfilTema(w http.ResponseWriter, r *http.Request) {
LokalnaPozadinaBlur: svezi.LokalnaPozadinaBlur, LokalnaPozadinaBlur: svezi.LokalnaPozadinaBlur,
LokalnaPozadinaBlurPozadine: svezi.LokalnaPozadinaBlurPozadine, LokalnaPozadinaBlurPozadine: svezi.LokalnaPozadinaBlurPozadine,
LokalnaPozadinaGlassOpacity: svezi.LokalnaPozadinaGlassOpacity, LokalnaPozadinaGlassOpacity: svezi.LokalnaPozadinaGlassOpacity,
LokalnaAnimacija: svezi.LokalnaAnimacija,
LokalniHover: svezi.LokalniHover,
} }
if podaci.LokalnaPozadinaOpacity == "" { if podaci.LokalnaPozadinaOpacity == "" {
podaci.LokalnaPozadinaOpacity = "50" podaci.LokalnaPozadinaOpacity = "50"
@@ -293,6 +295,84 @@ func (h *Handler) SacuvajLokalnuTemu(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther) http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
} }
// SacuvajLokalnuAnimaciju čuva korisnikovu preferencu animacije tabela
func (h *Handler) SacuvajLokalnuAnimaciju(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/tema", http.StatusSeeOther)
return
}
animacija := r.FormValue("lokalna_animacija")
dozvoljene := map[string]bool{"": true, "bez": true, "fadeInUp": true, "fadeIn": true, "scaleIn": true, "slideLeft": true}
if !dozvoljene[animacija] {
animacija = ""
}
if err := h.KorisniciRepo.SacuvajLokalnuAnimaciju(r.Context(), k.ID, animacija); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil/tema", http.StatusSeeOther)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Animacija je sačuvana.")
if ref := r.Referer(); ref != "" {
if u, err := url.Parse(ref); err == nil {
http.Redirect(w, r, u.RequestURI(), http.StatusSeeOther)
return
}
}
http.Redirect(w, r, "/admin/profil/tema", http.StatusSeeOther)
}
// SacuvajLokalniHover čuva korisnikovu preferencu hover efekta na karticama
func (h *Handler) SacuvajLokalniHover(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/tema", http.StatusSeeOther)
return
}
hover := r.FormValue("lokalni_hover")
dozvoljeni := map[string]bool{"": true, "bez": true, "podizanje": true, "svetlost": true, "zoom": true, "boja": true}
if !dozvoljeni[hover] {
hover = ""
}
if err := h.KorisniciRepo.SacuvajLokalniHover(r.Context(), k.ID, hover); err != nil {
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
http.Redirect(w, r, "/admin/profil/tema", http.StatusSeeOther)
return
}
middleware.SetFlash(w, r, h.DB, "uspeh", "Hover efekat je sačuvan.")
if ref := r.Referer(); ref != "" {
if u, err := url.Parse(ref); err == nil {
http.Redirect(w, r, u.RequestURI(), http.StatusSeeOther)
return
}
}
http.Redirect(w, r, "/admin/profil/tema", http.StatusSeeOther)
}
// ProfilOtpremiAvatar prima upload lične avatar slike korisnika // ProfilOtpremiAvatar prima upload lične avatar slike korisnika
func (h *Handler) ProfilOtpremiAvatar(w http.ResponseWriter, r *http.Request) { func (h *Handler) ProfilOtpremiAvatar(w http.ResponseWriter, r *http.Request) {
k := middleware.KorisnikIzKonteksta(r.Context()) k := middleware.KorisnikIzKonteksta(r.Context())
+35 -2
View File
@@ -110,9 +110,15 @@ func (h *Handler) NoviNalog(w http.ResponseWriter, r *http.Request) {
ps := h.popuniPodaciStranice(r, podesavanja) ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "servis" ps.Stranica = "servis"
ps.NaslovStranice = "Novi nalog" ps.NaslovStranice = "Novi nalog"
noviNalog := model.ServisniNalog{
BrojNaloga: brojNaloga,
Status: model.StatusPrimljeno,
DatumPrijema: time.Now(),
}
noviNalog.GarancijaDo = defaultGarancija(noviNalog.DatumPrijema, podesavanja)
h.renderujFormuNaloga(w, PodaciFormeNaloga{ h.renderujFormuNaloga(w, PodaciFormeNaloga{
PodaciStranice: ps, PodaciStranice: ps,
Nalog: model.ServisniNalog{BrojNaloga: brojNaloga, Status: model.StatusPrimljeno}, Nalog: noviNalog,
Klijenti: klijenti, Klijenti: klijenti,
Tehnicari: tehnicari, Tehnicari: tehnicari,
SviStatusi: model.SviStatusi, SviStatusi: model.SviStatusi,
@@ -206,6 +212,9 @@ func (h *Handler) IzmeniNalog(w http.ResponseWriter, r *http.Request) {
return return
} }
if nalog.GarancijaDo == nil {
nalog.GarancijaDo = defaultGarancija(nalog.DatumPrijema, podesavanja)
}
ps := h.popuniPodaciStranice(r, podesavanja) ps := h.popuniPodaciStranice(r, podesavanja)
ps.Stranica = "servis" ps.Stranica = "servis"
ps.NaslovStranice = "Izmeni nalog" ps.NaslovStranice = "Izmeni nalog"
@@ -460,6 +469,17 @@ func parseFormuNaloga(r *http.Request) (model.ServisniNalog, string) {
OpisKvara: opisKvara, OpisKvara: opisKvara,
Status: r.FormValue("status"), Status: r.FormValue("status"),
Napomena: strings.TrimSpace(r.FormValue("napomena")), Napomena: strings.TrimSpace(r.FormValue("napomena")),
Ostecenja: strings.TrimSpace(r.FormValue("ostecenja")),
PinUredjaja: strings.TrimSpace(r.FormValue("pin_uredjaja")),
Pribor: strings.TrimSpace(r.FormValue("pribor")),
DatumPrijema: time.Now(),
}
// datum prijema — korisnik može da unese drugi datum (npr. retroaktivno)
if dp := strings.TrimSpace(r.FormValue("datum_prijema")); dp != "" {
if t, err := time.Parse("2006-01-02", dp); err == nil {
nalog.DatumPrijema = t
}
} }
if nalog.Status == "" { if nalog.Status == "" {
@@ -493,16 +513,29 @@ func parseFormuNaloga(r *http.Request) (model.ServisniNalog, string) {
} }
} }
// opcioni datum garancije // opcioni datum garancije — preskačemo ako je korisnik označio "bez garancije"
if r.FormValue("bez_garancije") != "1" {
if gd := strings.TrimSpace(r.FormValue("garancija_do")); gd != "" { if gd := strings.TrimSpace(r.FormValue("garancija_do")); gd != "" {
if t, err := time.Parse("2006-01-02", gd); err == nil { if t, err := time.Parse("2006-01-02", gd); err == nil {
nalog.GarancijaDo = &t nalog.GarancijaDo = &t
} }
} }
}
return nalog, "" return nalog, ""
} }
// defaultGarancija vraća datum garancije na osnovu datuma prijema i podešavanja;
// vraća nil ako je rok 0 ili podešavanje nedostaje
func defaultGarancija(datumPrijema time.Time, podesavanja map[string]string) *time.Time {
meseci, err := strconv.Atoi(vrednostIliDefault(podesavanja, "servis_garancija_meseci", "2"))
if err != nil || meseci <= 0 {
return nil
}
t := datumPrijema.AddDate(0, meseci, 0)
return &t
}
// parseOpcionuCenu pretvara string u *float64 — prazno polje ili neispravna vrednost vraća nil // parseOpcionuCenu pretvara string u *float64 — prazno polje ili neispravna vrednost vraća nil
func parseOpcionuCenu(s string) *float64 { func parseOpcionuCenu(s string) *float64 {
s = strings.TrimSpace(s) s = strings.TrimSpace(s)
+2
View File
@@ -19,6 +19,8 @@ type Korisnik struct {
LokalnaPozadinaBlurPozadine string LokalnaPozadinaBlurPozadine string
LokalnaPozadinaGlassOpacity string LokalnaPozadinaGlassOpacity string
AvatarPutanja string AvatarPutanja string
LokalnaAnimacija string // "" | "fadeInUp" | "fadeIn" | "scaleIn" | "slideLeft"
LokalniHover string // "" | "bez" | "podizanje" | "svetlost"
} }
// Sesija predstavlja aktivnu sesiju prijavljenog korisnika // Sesija predstavlja aktivnu sesiju prijavljenog korisnika
+3
View File
@@ -43,6 +43,9 @@ type ServisniNalog struct {
GarancijaDo *time.Time GarancijaDo *time.Time
DatumPrijema time.Time DatumPrijema time.Time
DatumZavrsetka *time.Time DatumZavrsetka *time.Time
Ostecenja string
PinUredjaja string
Pribor string
} }
// ServisniDeo predstavlja jedan artikal ugrađen u servisni nalog // ServisniDeo predstavlja jedan artikal ugrađen u servisni nalog
+2
View File
@@ -53,6 +53,8 @@ type PodaciStranice struct {
AppPozadinaBlur string // vrednost 0-20 (px backdrop-filter blur na elementima) AppPozadinaBlur string // vrednost 0-20 (px backdrop-filter blur na elementima)
AppPozadinaBlurPozadine string // vrednost 0-20 (px filter blur na pozadinskoj slici) AppPozadinaBlurPozadine string // vrednost 0-20 (px filter blur na pozadinskoj slici)
AppPozadinaGlassOpacity string // vrednost 0-80 (% zatamnjivanje glass elemenata) — samo za ličnu pozadinu AppPozadinaGlassOpacity string // vrednost 0-80 (% zatamnjivanje glass elemenata) — samo za ličnu pozadinu
LokalnaAnimacija string // "" | "fadeInUp" | "fadeIn" | "scaleIn" | "slideLeft"
LokalniHover string // "" | "bez" | "podizanje" | "svetlost"
} }
// PodaciDashboarda su podaci specifični za dashboard stranicu // PodaciDashboarda su podaci specifični za dashboard stranicu
+3
View File
@@ -0,0 +1,3 @@
ALTER TABLE servisni_nalozi ADD COLUMN ostecenja TEXT;
ALTER TABLE servisni_nalozi ADD COLUMN pin_uredjaja TEXT;
ALTER TABLE servisni_nalozi ADD COLUMN pribor TEXT;
+2
View File
@@ -0,0 +1,2 @@
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES
('servis_garancija_meseci', '2');
+1
View File
@@ -0,0 +1 @@
ALTER TABLE korisnici ADD COLUMN lokalna_animacija TEXT;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE korisnici ADD COLUMN lokalni_hover TEXT;
+83 -7
View File
@@ -339,12 +339,42 @@ body {
border-radius: 12px; border-radius: 12px;
padding: 16px; padding: 16px;
box-shadow: var(--senka); box-shadow: var(--senka);
transition: transform 0.25s cubic-bezier(.4,0,.2,1), box-shadow 0.25s; transition: box-shadow 0.25s, border-color 0.25s, transform 0.25s, background 0.25s;
} }
.kartica:hover { .kartica:hover {
transform: scale(1.02); box-shadow: var(--senka-hover);
border-color: var(--ivica-jaka);
}
/* hover varijante po korisničkoj preferenciji.
Selektor je [data-hover] (ne body[data-hover]) da bi isti CSS
radio i globalno (body) i lokalno na preview wrapperima. */
[data-hover="bez"] .kartica:hover {
box-shadow: var(--senka); box-shadow: var(--senka);
border-color: var(--ivica);
transform: none;
}
[data-hover="podizanje"] .kartica:hover {
box-shadow: var(--senka-hover);
border-color: var(--ivica-jaka);
transform: translateY(-3px);
}
[data-hover="svetlost"] .kartica:hover {
box-shadow: 0 0 0 1.5px var(--sb-akcent), 0 0 20px color-mix(in srgb, var(--sb-akcent) 35%, transparent);
border-color: var(--sb-akcent);
transform: none;
}
[data-hover="zoom"] .kartica:hover {
box-shadow: var(--senka-hover);
border-color: var(--ivica-jaka);
transform: scale(1.02);
}
[data-hover="boja"] .kartica:hover {
box-shadow: var(--senka-hover);
border-color: var(--ivica-jaka);
background: color-mix(in srgb, var(--sb-akcent) 6%, var(--kartica));
transform: none;
} }
/* modal za premeštanje artikla koristi promenljive teme, pa prati svetlu/tamnu; /* modal za premeštanje artikla koristi promenljive teme, pa prati svetlu/tamnu;
@@ -592,7 +622,9 @@ body {
color: var(--tekst-sporedni); color: var(--tekst-sporedni);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
margin-bottom: 10px; padding-bottom: 8px;
margin-bottom: 12px;
border-bottom: 0.5px solid var(--ivica);
} }
/* avatar krug korisnika u topbaru */ /* avatar krug korisnika u topbaru */
@@ -980,6 +1012,21 @@ select {
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideLeft {
from { opacity: 0; transform: translateX(-12px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideDown { @keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); } from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
@@ -1003,11 +1050,40 @@ select {
animation: fadeInUp 0.2s ease backwards; animation: fadeInUp 0.2s ease backwards;
} }
/* kartice ne animiramo po defaultu — animacija je samo na redovima tabela */
.kartica.animiraj {
animation: none;
}
/* korisnikova preferencija animacije: body[data-animacija] nadjačava podrazumevano.
Kad korisnik odabere stil, animiraju se i redovi tabela i mobilne kartice. */
[data-animacija="bez"] .animiraj,
[data-animacija="bez"] .tabela tbody tr,
[data-animacija="bez"] .kartica.animiraj { animation: none; }
[data-animacija="fadeInUp"] .animiraj,
[data-animacija="fadeInUp"] .tabela tbody tr,
[data-animacija="fadeInUp"] .kartica.animiraj { animation: fadeInUp 0.2s ease backwards; }
[data-animacija="fadeIn"] .animiraj,
[data-animacija="fadeIn"] .tabela tbody tr,
[data-animacija="fadeIn"] .kartica.animiraj { animation: fadeIn 0.2s ease backwards; }
[data-animacija="scaleIn"] .animiraj,
[data-animacija="scaleIn"] .tabela tbody tr,
[data-animacija="scaleIn"] .kartica.animiraj { animation: scaleIn 0.2s ease backwards; }
[data-animacija="slideLeft"] .animiraj,
[data-animacija="slideLeft"] .tabela tbody tr,
[data-animacija="slideLeft"] .kartica.animiraj { animation: slideLeft 0.2s ease backwards; }
/* Stepenasta (stagger) animacija redova u svim listama JEDNO mesto za sve tabele. /* Stepenasta (stagger) animacija redova u svim listama JEDNO mesto za sve tabele.
Pogađa samo redove koji se animiraju (<tr class="animiraj">); ostali ostaju bez Animacija se primenjuje direktno na <tr> u .tabela, bez potrebe za .animiraj klasom.
delay-a. Mora biti u main.css: HTMX navigacija (hx-select) odbacuje <head>, pa Mora biti u main.css: HTMX navigacija (hx-select) odbacuje <head>, pa
stilovi iz page <style> blokova ne bi važili tokom navigacije. stilovi iz page <style> blokova ne bi važili tokom navigacije. */
Da promeniš animaciju redova svuda menjaš samo ovde. */ .tabela tbody tr {
animation: fadeInUp 0.2s ease backwards;
}
.tabela tbody tr:nth-child(1) { animation-delay: 0.04s; } .tabela tbody tr:nth-child(1) { animation-delay: 0.04s; }
.tabela tbody tr:nth-child(2) { animation-delay: 0.08s; } .tabela tbody tr:nth-child(2) { animation-delay: 0.08s; }
.tabela tbody tr:nth-child(3) { animation-delay: 0.12s; } .tabela tbody tr:nth-child(3) { animation-delay: 0.12s; }
+3 -1
View File
@@ -8,6 +8,7 @@
--tekst-sporedni: #57606a; --tekst-sporedni: #57606a;
--tekst-jak: #1f2328; --tekst-jak: #1f2328;
--ivica: #d0d7de; --ivica: #d0d7de;
--ivica-jaka: #9ba5b0;
--sb-akcent: #1f6feb; --sb-akcent: #1f6feb;
--sb-akcent-hover: #388bfd; --sb-akcent-hover: #388bfd;
--sb-aktivan: #ebeef1; --sb-aktivan: #ebeef1;
@@ -15,7 +16,8 @@
--upozorenje: #9a6700; --upozorenje: #9a6700;
--greska: #cf222e; --greska: #cf222e;
--info: #0969da; --info: #0969da;
--senka: 0 4px 12px rgba(0, 0, 0, 0.08); --senka: 0 4px 16px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.08);
--senka-hover: 0 8px 28px rgba(0, 0, 0, 0.18), 0 2px 8px rgba(0, 0, 0, 0.10);
--sidebar-pozadina: #ffffff; --sidebar-pozadina: #ffffff;
--poruka-uspeh-bg: rgba(26, 127, 55, 0.1); --poruka-uspeh-bg: rgba(26, 127, 55, 0.1);
--poruka-uspeh-boja: #1a7f37; --poruka-uspeh-boja: #1a7f37;
+3 -1
View File
@@ -8,6 +8,7 @@
--tekst-sporedni: #a0a8b5; --tekst-sporedni: #a0a8b5;
--tekst-jak: #f0f3f6; --tekst-jak: #f0f3f6;
--ivica: #353a44; --ivica: #353a44;
--ivica-jaka: #5a6070;
--sb-akcent: #4684d6; --sb-akcent: #4684d6;
--sb-akcent-hover: #5a96e6; --sb-akcent-hover: #5a96e6;
--sb-aktivan: #2a2f37; --sb-aktivan: #2a2f37;
@@ -15,7 +16,8 @@
--upozorenje: #e1b04a; --upozorenje: #e1b04a;
--greska: #cf5757; --greska: #cf5757;
--info: #5cb1d6; --info: #5cb1d6;
--senka: 0 4px 12px rgba(0, 0, 0, 0.4); --senka: 0 4px 16px rgba(0, 0, 0, 0.55), 0 1px 4px rgba(0, 0, 0, 0.35);
--senka-hover: 0 8px 28px rgba(0, 0, 0, 0.7), 0 2px 8px rgba(0, 0, 0, 0.45);
--sidebar-pozadina: #1a1c20; --sidebar-pozadina: #1a1c20;
--poruka-uspeh-bg: rgba(93, 184, 118, 0.15); --poruka-uspeh-bg: rgba(93, 184, 118, 0.15);
--poruka-uspeh-boja: #5db876; --poruka-uspeh-boja: #5db876;
+8 -3
View File
@@ -187,19 +187,19 @@
{{if index .Dozvole "podesavanja.pregled"}} {{if index .Dozvole "podesavanja.pregled"}}
<div> <div>
<button type="button" data-podmeni-dugme <button type="button" data-podmeni-dugme
class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}aktivan{{end}}" class="nav-stavka {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "podesavanja-servis") (eq .Stranica "dozvole")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;"> style="width:100%;background:none;border:none;cursor:pointer;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06-.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span>Podešavanja</span> <span>Podešavanja</span>
<span class="nav-strelica"> <span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}"> style="transition:transform 0.2s;transform:{{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "podesavanja-servis") (eq .Stranica "dozvole")}}rotate(180deg){{else}}rotate(0deg){{end}}">
<polyline points="6 9 12 15 18 9"/> <polyline points="6 9 12 15 18 9"/>
</svg> </svg>
</span> </span>
<span class="nav-tooltip">Podešavanja</span> <span class="nav-tooltip">Podešavanja</span>
</button> </button>
<div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "dozvole")}}otvoren{{end}}"> <div class="nav-podmeni {{if or (eq .Stranica "podesavanja") (eq .Stranica "podesavanja-opste") (eq .Stranica "podesavanja-izgled") (eq .Stranica "podesavanja-sistem") (eq .Stranica "podesavanja-kalkulacija-pdv") (eq .Stranica "podesavanja-servis") (eq .Stranica "dozvole")}}otvoren{{end}}">
<a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}"> <a href="/admin/podesavanja/opste" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-opste"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V7a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14"/><path d="M9 21v-5h6v5"/><path d="M9 9h.01M15 9h.01M9 13h.01M15 13h.01"/></svg>
<span>Opšte</span> <span>Opšte</span>
@@ -220,6 +220,11 @@
<span>Kalkulacija i PDV</span> <span>Kalkulacija i PDV</span>
<span class="nav-tooltip">Kalkulacija i PDV</span> <span class="nav-tooltip">Kalkulacija i PDV</span>
</a> </a>
<a href="/admin/podesavanja/servis" class="nav-stavka nav-podstavka {{if eq .Stranica "podesavanja-servis"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="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>
<span>Servis</span>
<span class="nav-tooltip">Servis</span>
</a>
{{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}} {{if or (eq .KorisnikUloga "superadmin") (eq .KorisnikUloga "admin")}}
<a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}"> <a href="/admin/dozvole" class="nav-stavka nav-podstavka {{if eq .Stranica "dozvole"}}aktivan{{end}}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
+2 -8
View File
@@ -6,12 +6,6 @@
<style> <style>
.izv-naslov { font-size: 13px; font-weight: 500; color: var(--tekst-sporedni); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 14px; } .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-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-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; } .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; }
@@ -34,9 +28,9 @@
<div class="izv-naslov" style="margin-bottom:0;">Mesečni prihod — poslednjih 12 meseci</div> <div class="izv-naslov" style="margin-bottom:0;">Mesečni prihod — poslednjih 12 meseci</div>
<div class="toggle-red" style="margin-bottom:0;"> <div class="toggle-red" style="margin-bottom:0;">
<span class="pomocni-tekst">Grafikon</span> <span class="pomocni-tekst">Grafikon</span>
<label class="toggle-switch"> <label class="toggl">
<input type="checkbox" id="toggle-grafikon" checked> <input type="checkbox" id="toggle-grafikon" checked>
<span class="toggle-slider"></span> <span class="toggl-klizac"></span>
</label> </label>
</div> </div>
</div> </div>
+1 -1
View File
@@ -24,7 +24,7 @@
<!-- istorija nivelacija --> <!-- istorija nivelacija -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;"> <div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:760px;"> <table class="tabela" style="width:100%;border-collapse:collapse;font-size:13px;min-width:760px;">
<thead> <thead>
<tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);"> <tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 12px;">Datum</th> <th style="padding:10px 12px;">Datum</th>
+1 -1
View File
@@ -27,7 +27,7 @@
<!-- knjiga --> <!-- knjiga -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;"> <div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:900px;"> <table class="tabela" style="width:100%;border-collapse:collapse;font-size:13px;min-width:900px;">
<thead> <thead>
<tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);"> <tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 12px;">Datum prometa</th> <th style="padding:10px 12px;">Datum prometa</th>
+1 -1
View File
@@ -27,7 +27,7 @@
<!-- knjiga --> <!-- knjiga -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;"> <div class="kartica animiraj" style="padding:0;overflow:hidden;">
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:980px;"> <table class="tabela" style="width:100%;border-collapse:collapse;font-size:13px;min-width:980px;">
<thead> <thead>
<tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);"> <tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 12px;">Datum prometa</th> <th style="padding:10px 12px;">Datum prometa</th>
+2 -2
View File
@@ -23,7 +23,7 @@
<!-- obračun po stopama --> <!-- obračun po stopama -->
<div class="kartica animiraj" style="padding:0;overflow:hidden;margin-bottom:16px;"> <div class="kartica animiraj" style="padding:0;overflow:hidden;margin-bottom:16px;">
<div style="overflow-x:auto;"> <div style="overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:560px;"> <table class="tabela" style="width:100%;border-collapse:collapse;font-size:13px;min-width:560px;">
<thead> <thead>
<tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);"> <tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 12px;"></th> <th style="padding:10px 12px;"></th>
@@ -110,7 +110,7 @@
</div> </div>
</div> </div>
<div style="overflow-x:auto;margin-top:12px;"> <div style="overflow-x:auto;margin-top:12px;">
<table style="width:100%;border-collapse:collapse;font-size:13px;min-width:560px;"> <table class="tabela" style="width:100%;border-collapse:collapse;font-size:13px;min-width:560px;">
<thead> <thead>
<tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);"> <tr style="text-align:left;color:var(--tekst-sporedni);border-bottom:0.5px solid var(--ivica);">
<th style="padding:10px 12px;width:48px;">Polje</th> <th style="padding:10px 12px;width:48px;">Polje</th>
@@ -0,0 +1,45 @@
{{template "base" .}}
{{define "naslov"}}Podešavanja — Servis — NTech{{end}}
{{define "sadrzaj"}}
<div class="stranica-stack" style="width:100%;max-width:100%;">
{{if .Sacuvano}}
<div class="poruka-uspeh">Podešavanja su uspešno sačuvana.</div>
{{end}}
<form method="POST" action="/podesavanja/sacuvaj">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<input type="hidden" name="_next" value="/admin/podesavanja/servis">
<div class="kartica animiraj">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Servis</span>
</div>
<div class="kolona" style="gap:16px;">
<div style="max-width:280px;">
<label for="servis_garancija_meseci" class="polje-labela">
Podrazumevani rok garancije (meseci)
</label>
<input type="number" id="servis_garancija_meseci" name="servis_garancija_meseci"
min="0" max="120" value="{{.ServisGarancijaMeseci}}"
style="width:100%;">
<div class="pomocni-tekst" style="margin-top:6px;">
Automatski se upisuje pri prijemu uređaja. Vrednost 0 znači bez garancije.
</div>
</div>
</div>
<div style="display:flex;justify-content:flex-end;margin-top:20px;">
<button type="submit" class="btn-primarno">Sačuvaj</button>
</div>
</div>
</form>
</div>
{{end}}
+145
View File
@@ -74,6 +74,104 @@
{{end}} {{end}}
</div> </div>
<!-- kartica: animacije -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12s2.545-5 7-5c4.454 0 7 5 7 5s-2.546 5-7 5c-4.455 0-7-5-7-5z"/><circle cx="12" cy="12" r="3"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Animacije tabela</span>
</div>
<form method="POST" action="/profil/animacija">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div class="kolona" style="gap:16px;">
<div style="max-width:300px;">
<label class="polje-labela" for="anim-select">Vrsta animacije pri učitavanju</label>
<select id="anim-select" name="lokalna_animacija" style="width:100%;" onchange="animPreview(this.value)"><!-- preview se primenjuje na wrapper, ne na body -->
<option value="" {{if eq .LokalnaAnimacija ""}}selected{{end}}>Podrazumevano (klizanje gore)</option>
<option value="bez" {{if eq .LokalnaAnimacija "bez"}}selected{{end}}>Bez animacije</option>
<option value="fadeInUp" {{if eq .LokalnaAnimacija "fadeInUp"}}selected{{end}}>Klizanje gore (fadeInUp)</option>
<option value="fadeIn" {{if eq .LokalnaAnimacija "fadeIn"}}selected{{end}}>Pojavljivanje (fadeIn)</option>
<option value="scaleIn" {{if eq .LokalnaAnimacija "scaleIn"}}selected{{end}}>Zumiranje (scaleIn)</option>
<option value="slideLeft" {{if eq .LokalnaAnimacija "slideLeft"}}selected{{end}}>Klizanje s leva (slideLeft)</option>
</select>
</div>
<!-- preview -->
<div>
<div class="pomocni-tekst" style="margin-bottom:8px;">Pregled:</div>
<!-- preview wrapper nosi data-animacija atribut; isti CSS [data-animacija] .animiraj
koji radi globalno na body radi i ovde, ali izolovano — pogađa SAMO redove unutar wrappera -->
<div id="anim-preview-wrap" style="border:0.5px solid var(--ivica);border-radius:8px;overflow:hidden;max-width:340px;">
<table style="width:100%;border-collapse:collapse;">
<tbody id="anim-preview-body">
<tr class="animiraj" style="border-bottom:0.5px solid var(--ivica);"><td style="padding:8px 12px;font-size:13px;color:var(--tekst-glavni);">Laptop HP 840</td><td style="padding:8px 12px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">120.000 din</td></tr>
<tr class="animiraj" style="border-bottom:0.5px solid var(--ivica);animation-delay:0.08s;"><td style="padding:8px 12px;font-size:13px;color:var(--tekst-glavni);">iPhone 14 Pro</td><td style="padding:8px 12px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">95.000 din</td></tr>
<tr class="animiraj" style="animation-delay:0.16s;"><td style="padding:8px 12px;font-size:13px;color:var(--tekst-glavni);">Samsung S24</td><td style="padding:8px 12px;text-align:right;font-size:13px;color:var(--tekst-sporedni);">80.000 din</td></tr>
</tbody>
</table>
</div>
<button type="button" onclick="animPreviewPonovi()"
style="margin-top:8px;padding:6px 14px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;font-size:12px;color:var(--tekst-sporedni);cursor:pointer;">
Ponovi pregled
</button>
</div>
<div>
<button type="submit" class="btn-primarno">Sačuvaj</button>
</div>
</div>
</form>
</div>
<!-- kartica: hover efekat -->
<div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--sb-akcent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3l14 9-7 1-4 7z"/></svg>
<span style="font-size:15px;font-weight:500;color:var(--tekst-glavni);">Hover efekat kartica</span>
</div>
<form method="POST" action="/profil/hover">
<input type="hidden" name="_csrf" value="{{.CsrfToken}}">
<div class="kolona" style="gap:16px;">
<div style="max-width:300px;">
<label class="polje-labela" for="hover-select">Efekat pri prelasku mišem</label>
<select id="hover-select" name="lokalni_hover" style="width:100%;" onchange="hoverPreview(this.value)">
<option value="" {{if eq .LokalniHover ""}}selected{{end}}>Podrazumevano (senka + ivica)</option>
<option value="bez" {{if eq .LokalniHover "bez"}}selected{{end}}>Bez efekta</option>
<option value="podizanje" {{if eq .LokalniHover "podizanje"}}selected{{end}}>Podizanje (lift)</option>
<option value="svetlost" {{if eq .LokalniHover "svetlost"}}selected{{end}}>Svetlost ivice (akcent)</option>
<option value="zoom" {{if eq .LokalniHover "zoom"}}selected{{end}}>Zoom (uvećanje)</option>
<option value="boja" {{if eq .LokalniHover "boja"}}selected{{end}}>Boja pozadine (akcent)</option>
</select>
</div>
<!-- preview -->
<div>
<div class="pomocni-tekst" style="margin-bottom:8px;">Pregled — pređi mišem:</div>
<div id="hover-preview-wrap" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;max-width:380px;">
<div id="hprev1" class="kartica" style="padding:12px;cursor:default;min-height:64px;display:flex;flex-direction:column;gap:4px;">
<div style="font-size:11px;color:var(--tekst-sporedni);">Artikli</div>
<div style="font-size:18px;font-weight:600;color:var(--tekst-glavni);">1.284</div>
</div>
<div id="hprev2" class="kartica" style="padding:12px;cursor:default;min-height:64px;display:flex;flex-direction:column;gap:4px;">
<div style="font-size:11px;color:var(--tekst-sporedni);">Servis</div>
<div style="font-size:18px;font-weight:600;color:var(--tekst-glavni);">47</div>
</div>
<div id="hprev3" class="kartica" style="padding:12px;cursor:default;min-height:64px;display:flex;flex-direction:column;gap:4px;">
<div style="font-size:11px;color:var(--tekst-sporedni);">Prihod</div>
<div style="font-size:18px;font-weight:600;color:var(--tekst-glavni);">+12%</div>
</div>
</div>
<div class="pomocni-tekst" style="margin-top:6px;font-size:11px;">Efekat se primenjuje odmah pri odabiru.</div>
</div>
<div>
<button type="submit" class="btn-primarno">Sačuvaj</button>
</div>
</div>
</form>
</div>
<!-- kartica: moj avatar --> <!-- kartica: moj avatar -->
<div class="kartica animiraj" style="margin-bottom:16px;"> <div class="kartica animiraj" style="margin-bottom:16px;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);"> <div style="display:flex;align-items:center;gap:10px;margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
@@ -255,4 +353,51 @@
{{end}} {{end}}
</div> </div>
<script>
function hoverPreview(val) {
var wrap = document.getElementById('hover-preview-wrap');
if (!wrap) return;
if (val === '') {
wrap.removeAttribute('data-hover');
} else {
wrap.setAttribute('data-hover', val);
}
}
// inicijalizuj preview na osnovu trenutnog odabira pri učitavanju
(function() {
var sel = document.getElementById('hover-select');
if (sel) hoverPreview(sel.value);
})();
// mapiranje vrednosti dropdowna na vrednost data-animacija atributa.
// prazna vrednost ("Podrazumevano") odgovara fadeInUp — istom podrazumevanom stilu.
var animVrednosti = {
'': 'fadeInUp',
'bez': 'bez',
'fadeInUp': 'fadeInUp',
'fadeIn': 'fadeIn',
'scaleIn': 'scaleIn',
'slideLeft': 'slideLeft'
};
// postavlja data-animacija na PREVIEW wrapper (ne na body), pa CSS
// [data-animacija] .animiraj radi izolovano — samo nad redovima preview tabele.
function animPreview(val) {
var wrap = document.getElementById('anim-preview-wrap');
if (!wrap) return;
var anim = animVrednosti[val] || 'fadeInUp';
// restartuj animaciju: skini atribut, izazovi reflow, pa ga vrati
wrap.removeAttribute('data-animacija');
void wrap.offsetHeight; // reflow da bi se animacija ponovo okinula
wrap.setAttribute('data-animacija', anim);
}
// "Ponovi pregled" — ponovo okida animaciju za trenutno izabrani stil
function animPreviewPonovi() {
animPreview(document.getElementById('anim-select').value);
}
// inicijalizuj preview na osnovu sačuvane preferencije pri učitavanju stranice
(function() {
var sel = document.getElementById('anim-select');
if (sel) animPreview(sel.value);
})();
</script>
{{end}} {{end}}
@@ -103,6 +103,28 @@
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:6px;">Opis kvara</div> <div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:6px;">Opis kvara</div>
<div style="font-size:14px;color:var(--tekst-glavni);line-height:1.6;white-space:pre-wrap;">{{.Nalog.OpisKvara}}</div> <div style="font-size:14px;color:var(--tekst-glavni);line-height:1.6;white-space:pre-wrap;">{{.Nalog.OpisKvara}}</div>
</div> </div>
{{if or .Nalog.Ostecenja .Nalog.PinUredjaja .Nalog.Pribor}}
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(160px, 1fr));gap:16px;">
{{if .Nalog.Ostecenja}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Oštećenja pri prijemu</div>
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.Ostecenja}}</div>
</div>
{{end}}
{{if .Nalog.PinUredjaja}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">PIN / lozinka uređaja</div>
<div style="font-size:14px;color:var(--tekst-glavni);font-family:monospace;">{{.Nalog.PinUredjaja}}</div>
</div>
{{end}}
{{if .Nalog.Pribor}}
<div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:4px;">Pribor i oprema</div>
<div style="font-size:14px;color:var(--tekst-glavni);">{{.Nalog.Pribor}}</div>
</div>
{{end}}
</div>
{{end}}
{{if .Nalog.Napomena}} {{if .Nalog.Napomena}}
<div> <div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:6px;">Napomena</div> <div style="font-size:12px;color:var(--tekst-sporedni);margin-bottom:6px;">Napomena</div>
+64 -7
View File
@@ -12,11 +12,11 @@
</a> </a>
<div class="kartica forma-kartica animiraj"> <div class="kartica forma-kartica animiraj">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:14px;border-bottom:0.5px solid var(--ivica);flex-wrap:wrap;gap:10px;"> <div style="display:flex;justify-content:space-between;align-items:center;margin:-16px -16px 20px -16px;padding:12px 16px;background:var(--pozadina);border-radius:12px 12px 0 0;flex-wrap:wrap;gap:10px;">
<span style="font-size:16px;font-weight:500;color:var(--tekst-glavni);"> <span style="font-size:15px;font-weight:600;color:var(--tekst-glavni);">
{{if .Izmena}}Izmeni nalog{{else}}Novi nalog{{end}} {{if .Izmena}}Izmeni nalog{{else}}Novi nalog{{end}}
</span> </span>
<span style="font-size:14px;font-weight:500;color:var(--tekst-sporedni);font-family:monospace;"> <span style="font-size:13px;font-weight:500;color:var(--tekst-sporedni);font-family:monospace;">
{{.Nalog.BrojNaloga}} {{.Nalog.BrojNaloga}}
</span> </span>
</div> </div>
@@ -60,6 +60,26 @@
placeholder="Opišite problem koji je klijent prijavio..." placeholder="Opišite problem koji je klijent prijavio..."
style="width:100%;resize:vertical;">{{.Nalog.OpisKvara}}</textarea> style="width:100%;resize:vertical;">{{.Nalog.OpisKvara}}</textarea>
</div> </div>
<div class="forma-grid-2" style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label class="polje-labela">Oštećenja pri prijemu</label>
<input type="text" name="ostecenja" value="{{.Nalog.Ostecenja}}"
placeholder="npr. ogrebotina na ekranu"
style="width:100%;">
</div>
<div>
<label class="polje-labela">PIN / lozinka uređaja</label>
<input type="text" name="pin_uredjaja" value="{{.Nalog.PinUredjaja}}"
placeholder="npr. 1234"
style="width:100%;">
</div>
</div>
<div>
<label class="polje-labela">Pribor i oprema</label>
<input type="text" name="pribor" value="{{.Nalog.Pribor}}"
placeholder="npr. punjač, torbica, miš"
style="width:100%;">
</div>
</div> </div>
</div> </div>
@@ -97,8 +117,8 @@
<!-- status i datumi --> <!-- status i datumi -->
<div> <div>
<div class="sekcija-naslov">Status i datumi</div> <div class="sekcija-naslov">Status i datumi</div>
<div class="forma-grid-4" style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:12px;"> <div class="kolona" style="gap:12px;">
<div style="grid-column:span 2;"> <div>
<label class="polje-labela">Status naloga</label> <label class="polje-labela">Status naloga</label>
<select name="status" style="width:100%;"> <select name="status" style="width:100%;">
{{range .SviStatusi}} {{range .SviStatusi}}
@@ -106,17 +126,38 @@
{{end}} {{end}}
</select> </select>
</div> </div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;">
<div>
<label class="polje-labela">Datum prijema</label>
<input type="date" name="datum_prijema"
value="{{.Nalog.DatumPrijema.Format "2006-01-02"}}"
style="width:100%;">
</div>
<div> <div>
<label class="polje-labela">Datum završetka</label> <label class="polje-labela">Datum završetka</label>
<input type="date" name="datum_zavrsetka" <input type="date" name="datum_zavrsetka"
value="{{if .Nalog.DatumZavrsetka}}{{.Nalog.DatumZavrsetka.Format "2006-01-02"}}{{end}}" value="{{if .Nalog.DatumZavrsetka}}{{.Nalog.DatumZavrsetka.Format "2006-01-02"}}{{end}}"
style="width:100%;"> style="width:100%;">
</div> </div>
</div>
<div> <div>
<label class="polje-labela">Garancija do</label> <label class="polje-labela">Garancija do</label>
<input type="date" name="garancija_do" <input type="date" name="garancija_do" id="garancija_do_input"
value="{{if .Nalog.GarancijaDo}}{{.Nalog.GarancijaDo.Format "2006-01-02"}}{{end}}" value="{{if .Nalog.GarancijaDo}}{{.Nalog.GarancijaDo.Format "2006-01-02"}}{{end}}"
style="width:100%;"> {{if not .Nalog.GarancijaDo}}disabled{{end}}
style="width:100%;{{if not .Nalog.GarancijaDo}}opacity:0.4;{{end}}">
</div>
<!-- svič bez garancije -->
<div style="display:flex;align-items:center;gap:10px;">
<label class="toggl" style="flex-shrink:0;">
<input type="checkbox" id="bez_garancije_chk"
{{if not .Nalog.GarancijaDo}}checked{{end}}
onchange="toggleGarancija(this)">
<span class="toggl-klizac"></span>
</label>
<input type="hidden" name="bez_garancije" id="bez_garancije_val"
value="{{if not .Nalog.GarancijaDo}}1{{else}}0{{end}}">
<span style="font-size:13px;color:var(--tekst-sporedni);">Bez garancije</span>
</div> </div>
</div> </div>
</div> </div>
@@ -172,4 +213,20 @@
</form> </form>
</div> </div>
</div> </div>
<script>
function toggleGarancija(chk) {
var input = document.getElementById('garancija_do_input');
var hidden = document.getElementById('bez_garancije_val');
if (chk.checked) {
input.value = '';
input.disabled = true;
input.style.opacity = '0.4';
hidden.value = '1';
} else {
input.disabled = false;
input.style.opacity = '1';
hidden.value = '0';
}
}
</script>
{{end}} {{end}}
+1 -1
View File
@@ -106,7 +106,7 @@
</script> </script>
{{end}} {{end}}
</head> </head>
<body> <body{{if .LokalnaAnimacija}} data-animacija="{{.LokalnaAnimacija}}"{{end}}{{if .LokalniHover}} data-hover="{{.LokalniHover}}"{{end}}>
<div class="raspored"> <div class="raspored">
<div class="sidebar-overlay" id="sidebar-overlay"></div> <div class="sidebar-overlay" id="sidebar-overlay"></div>
{{template "sidebar" .}} {{template "sidebar" .}}