Magacin premeštanje, backup podešavanja, čišćenje RBAC sistema
Magacin:
- Dodato premeštanje artikla u drugu kategoriju (dugme + nativni
<details> meni, bez JS-a; radi na desktopu i mobilnom)
- Endpoint POST /magacin/premesti/{id} uz proveru dozvole artikal.premesti
Backup:
- Nova podešavanja: interval automatskog backupa i broj kopija (rotacija)
- Periodični backup uz onaj pri pokretanju; interval se čita iz baze
- Migracija 037_backup_podesavanja.sql
Dozvole (RBAC):
- Dodate kartice koje su nedostajale (dashboard.prihod, prodaja.storno,
podesavanja.login_pozadina, tema.lokalno) — popravljen i bug gde su se
gasile pri svakom čuvanju matrice
- Aktivirana kontrola pregleda za prodaju, servis, klijente i dobavljače
(provera u handlerima + skrivanje iz sidebara)
- Uklonjene mrtve/obmanjujuće dozvole iz matrice i sveAkcije (korisnici,
podsetnici, artikal.pregled, kategorija.izmeni, tema.globalno,
podesavanja.app_pozadina); sveAkcije 47 -> 34
- Čišćenje zastarelih redova (siročića) u tabeli dozvola pri startu
Ostalo:
- Statički fajlovi: embed celog web/static i ispravan MIME za .js/.css
- Keš šablona: dodat admin_dozvole (stranica Dozvole se nije otvarala)
- Sidebar accordion: radi i skupljen i proširen, međusobno isključiv
This commit is contained in:
@@ -5,7 +5,7 @@ import "embed"
|
|||||||
//go:embed migrations
|
//go:embed migrations
|
||||||
var MigracijeFS embed.FS
|
var MigracijeFS embed.FS
|
||||||
|
|
||||||
//go:embed web/static/css
|
//go:embed web/static
|
||||||
var StaticFS embed.FS
|
var StaticFS embed.FS
|
||||||
|
|
||||||
//go:embed web/templates
|
//go:embed web/templates
|
||||||
|
|||||||
+42
-11
@@ -2,13 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"mime"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ntech"
|
"ntech"
|
||||||
@@ -27,6 +30,8 @@ import (
|
|||||||
var Verzija = "dev"
|
var Verzija = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
mime.AddExtensionType(".js", "text/javascript")
|
||||||
|
mime.AddExtensionType(".css", "text/css")
|
||||||
godotenv.Load("ntech.env")
|
godotenv.Load("ntech.env")
|
||||||
auth.InitAuthLog()
|
auth.InitAuthLog()
|
||||||
|
|
||||||
@@ -80,7 +85,24 @@ func main() {
|
|||||||
log.Printf("Upozorenje: greška pri inicijalizaciji dozvola: %v", err)
|
log.Printf("Upozorenje: greška pri inicijalizaciji dozvola: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
napraviStartupBackup(putanjaBaze)
|
// ukloni zastarele dozvole (siročiće) koje više ne postoje u kodu
|
||||||
|
if br, err := sqlite.OcistiSirociceDoz(context.Background(), db, ntechmw.SveAkcije()); err != nil {
|
||||||
|
log.Printf("Upozorenje: greška pri čišćenju dozvola: %v", err)
|
||||||
|
} else if br > 0 {
|
||||||
|
log.Printf("Dozvole: uklonjeno %d zastarelih redova", br)
|
||||||
|
}
|
||||||
|
|
||||||
|
napraviBackup(db, putanjaBaze)
|
||||||
|
|
||||||
|
// periodični automatski backup — interval se čita iz podešavanja u svakom ciklusu,
|
||||||
|
// tako da izmena u podešavanjima stupa na snagu bez restarta (od sledećeg ciklusa)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
sati := procitajIntPodesavanje(db, "backup_interval_sati", 24)
|
||||||
|
time.Sleep(time.Duration(sati) * time.Hour)
|
||||||
|
napraviBackup(db, putanjaBaze)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// periodično brisanje isteklih sesija i starih pokušaja prijave
|
// periodično brisanje isteklih sesija i starih pokušaja prijave
|
||||||
go func() {
|
go func() {
|
||||||
@@ -164,6 +186,7 @@ func main() {
|
|||||||
r.Get("/magacin/izmeni/{id}", h.IzmeniArtikal)
|
r.Get("/magacin/izmeni/{id}", h.IzmeniArtikal)
|
||||||
r.Post("/magacin/izmeni/{id}", h.SacuvajIzmenuArtikla)
|
r.Post("/magacin/izmeni/{id}", h.SacuvajIzmenuArtikla)
|
||||||
r.Get("/magacin/obrisi/{id}", h.ObrisiArtikal)
|
r.Get("/magacin/obrisi/{id}", h.ObrisiArtikal)
|
||||||
|
r.Post("/magacin/premesti/{id}", h.PremestiArtikal)
|
||||||
r.Get("/magacin/kategorije", h.Kategorije)
|
r.Get("/magacin/kategorije", h.Kategorije)
|
||||||
r.Post("/magacin/kategorije/dodaj", h.DodajKategoriju)
|
r.Post("/magacin/kategorije/dodaj", h.DodajKategoriju)
|
||||||
r.Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju)
|
r.Get("/magacin/kategorije/obrisi/{id}", h.ObrisiKategoriju)
|
||||||
@@ -250,8 +273,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// napraviStartupBackup kreira kopiju baze pri pokretanju i čuva poslednjih 7
|
// napraviBackup kreira konzistentnu kopiju baze i briše najstarije preko zadatog broja kopija.
|
||||||
func napraviStartupBackup(putanjaBaze string) {
|
// Koristi već otvorenu vezu ka bazi (VACUUM INTO je bezbedan na pooled konekciji).
|
||||||
|
func napraviBackup(db *sql.DB, putanjaBaze string) {
|
||||||
if _, err := os.Stat(putanjaBaze); os.IsNotExist(err) {
|
if _, err := os.Stat(putanjaBaze); os.IsNotExist(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -265,20 +289,27 @@ func napraviStartupBackup(putanjaBaze string) {
|
|||||||
ime := fmt.Sprintf("ntech_%s.db", time.Now().Format("20060102_150405"))
|
ime := fmt.Sprintf("ntech_%s.db", time.Now().Format("20060102_150405"))
|
||||||
odrediste := filepath.Join(folder, ime)
|
odrediste := filepath.Join(folder, ime)
|
||||||
|
|
||||||
db, err := sqlite.OtvoriDB(putanjaBaze)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("backup: ne mogu otvoriti bazu: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
if _, err := db.ExecContext(context.Background(), "VACUUM INTO ?", odrediste); err != nil {
|
if _, err := db.ExecContext(context.Background(), "VACUUM INTO ?", odrediste); err != nil {
|
||||||
log.Printf("backup: greška pri pravljenju backup-a: %v", err)
|
log.Printf("backup: greška pri pravljenju backup-a: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Backup kreiran: %s", odrediste)
|
log.Printf("Backup kreiran: %s", odrediste)
|
||||||
ocistiStareBackupe(folder, 7)
|
ocistiStareBackupe(folder, procitajIntPodesavanje(db, "backup_broj_kopija", 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
// procitajIntPodesavanje vraća celobrojnu vrednost podešavanja iz baze,
|
||||||
|
// ili podrazumevanu ako ključ ne postoji ili nije validan pozitivan broj
|
||||||
|
func procitajIntPodesavanje(db *sql.DB, kljuc string, podrazumevano int) int {
|
||||||
|
v, err := sqlite.DohvatiPodesavanje(context.Background(), db, kljuc)
|
||||||
|
if err != nil || v == "" {
|
||||||
|
return podrazumevano
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(v)
|
||||||
|
if err != nil || n < 1 {
|
||||||
|
return podrazumevano
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// ocistiStareBackupe briše najstarije backup fajlove ako ih ima više od max
|
// ocistiStareBackupe briše najstarije backup fajlove ako ih ima više od max
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type ArtikalRepository interface {
|
|||||||
DohvatiID(ctx context.Context, id int64) (*model.Artikal, error)
|
DohvatiID(ctx context.Context, id int64) (*model.Artikal, error)
|
||||||
Kreiraj(ctx context.Context, a *model.Artikal) (int64, error)
|
Kreiraj(ctx context.Context, a *model.Artikal) (int64, error)
|
||||||
Izmeni(ctx context.Context, a *model.Artikal) error
|
Izmeni(ctx context.Context, a *model.Artikal) error
|
||||||
|
PremestiKategoriju(ctx context.Context, id int64, kategorijaID *int64) error
|
||||||
Obrisi(ctx context.Context, id int64) error
|
Obrisi(ctx context.Context, id int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,17 @@ func (r *ArtikalRepo) Izmeni(ctx context.Context, a *model.Artikal) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PremestiKategoriju menja samo kategoriju artikla (premeštanje u drugu kategoriju).
|
||||||
|
// kategorijaID može biti nil — tada artikal ostaje bez kategorije.
|
||||||
|
func (r *ArtikalRepo) PremestiKategoriju(ctx context.Context, id int64, kategorijaID *int64) error {
|
||||||
|
_, err := r.db.ExecContext(ctx,
|
||||||
|
"UPDATE artikli SET kategorija_id = ? WHERE id = ?", kategorijaID, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ntech: ArtikalRepo.PremestiKategoriju: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Obrisi briše artikal po ID-u
|
// Obrisi briše artikal po ID-u
|
||||||
func (r *ArtikalRepo) Obrisi(ctx context.Context, id int64) error {
|
func (r *ArtikalRepo) Obrisi(ctx context.Context, id int64) error {
|
||||||
_, err := r.db.ExecContext(ctx, "DELETE FROM artikli WHERE id = ?", id)
|
_, err := r.db.ExecContext(ctx, "DELETE FROM artikli WHERE id = ?", id)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sqliteDozvoleRepo struct {
|
type sqliteDozvoleRepo struct {
|
||||||
@@ -27,6 +28,28 @@ func InicijalizujDozvole(ctx context.Context, db *sql.DB, defaultFn func(uloga,
|
|||||||
return popuniPodrazumevano(ctx, db, defaultFn, sveAkcije)
|
return popuniPodrazumevano(ctx, db, defaultFn, sveAkcije)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OcistiSirociceDoz briše iz tabele dozvola redove čija akcija više ne postoji
|
||||||
|
// u sveAkcije (npr. nakon uklanjanja zastarele dozvole iz koda). Vraća broj obrisanih.
|
||||||
|
func OcistiSirociceDoz(ctx context.Context, db *sql.DB, sveAkcije []string) (int64, error) {
|
||||||
|
if len(sveAkcije) == 0 {
|
||||||
|
// sigurnosna brana — bez liste bismo obrisali sve, što ne želimo
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
placeholders := strings.Repeat("?,", len(sveAkcije))
|
||||||
|
placeholders = strings.TrimSuffix(placeholders, ",")
|
||||||
|
args := make([]any, len(sveAkcije))
|
||||||
|
for i, a := range sveAkcije {
|
||||||
|
args[i] = a
|
||||||
|
}
|
||||||
|
rezultat, err := db.ExecContext(ctx,
|
||||||
|
`DELETE FROM dozvole WHERE akcija NOT IN (`+placeholders+`)`, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("ntech: dozvole.OcistiSirocice: %w", err)
|
||||||
|
}
|
||||||
|
br, _ := rezultat.RowsAffected()
|
||||||
|
return br, nil
|
||||||
|
}
|
||||||
|
|
||||||
// popuniPodrazumevano upisuje sve podrazumevane dozvole u bazu
|
// popuniPodrazumevano upisuje sve podrazumevane dozvole u bazu
|
||||||
func popuniPodrazumevano(ctx context.Context, db *sql.DB, defaultFn func(uloga, akcija string) bool, sveAkcije []string) error {
|
func popuniPodrazumevano(ctx context.Context, db *sql.DB, defaultFn func(uloga, akcija string) bool, sveAkcije []string) error {
|
||||||
for _, uloga := range []string{"radnik", "admin", "superadmin"} {
|
for _, uloga := range []string{"radnik", "admin", "superadmin"} {
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ type PodaciFormeDobavljaca struct {
|
|||||||
|
|
||||||
// Dobavljaci renderuje listu svih dobavljača
|
// Dobavljaci renderuje listu svih dobavljača
|
||||||
func (h *Handler) Dobavljaci(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Dobavljaci(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "dobavljac.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled dobavljača.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var bazniSabloni = []string{
|
|||||||
|
|
||||||
// saSidebar su šabloni koji koriste base layout (sidebar + topbar)
|
// saSidebar su šabloni koji koriste base layout (sidebar + topbar)
|
||||||
var saSidebar = []string{
|
var saSidebar = []string{
|
||||||
"admin_korisnici", "admin_profil", "admin_login_istorija",
|
"admin_korisnici", "admin_profil", "admin_login_istorija", "admin_dozvole",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
"dobavljaci", "dobavljac_forma",
|
"dobavljaci", "dobavljac_forma",
|
||||||
"izvestaji",
|
"izvestaji",
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ type PodaciFormeKlijenta struct {
|
|||||||
|
|
||||||
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
|
// Klijenti renderuje listu svih klijenata sa opcionom pretragom
|
||||||
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "klijent.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled klijenata.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type PodaciMagacina struct {
|
|||||||
KategorijaIDStr string
|
KategorijaIDStr string
|
||||||
Sacuvano bool
|
Sacuvano bool
|
||||||
Obrisan bool
|
Obrisan bool
|
||||||
|
Premesten bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Magacin renderuje listu artikala
|
// Magacin renderuje listu artikala
|
||||||
@@ -68,11 +69,44 @@ func (h *Handler) Magacin(w http.ResponseWriter, r *http.Request) {
|
|||||||
KategorijaIDStr: katIDStr,
|
KategorijaIDStr: katIDStr,
|
||||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
||||||
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
Obrisan: r.URL.Query().Get("obrisan") == "1",
|
||||||
|
Premesten: r.URL.Query().Get("premesten") == "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderujTemplate(w, "magacin", podaci)
|
h.renderujTemplate(w, "magacin", podaci)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PremestiArtikal menja kategoriju artikla (premeštanje u drugu kategoriju).
|
||||||
|
// Prazno polje kategorija_id znači premeštanje u "bez kategorije".
|
||||||
|
func (h *Handler) PremestiArtikal(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "artikal.premesti") {
|
||||||
|
http.Error(w, "Nemate dozvolu za ovu akciju.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Neispravan ID artikla", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var kategorijaID *int64
|
||||||
|
if v := r.FormValue("kategorija_id"); v != "" {
|
||||||
|
kid, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Neispravna kategorija", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kategorijaID = &kid
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Artikli.PremestiKategoriju(r.Context(), id, kategorijaID); err != nil {
|
||||||
|
http.Error(w, "Greška pri premeštanju artikla", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/magacin?premesten=1", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
// ObrisiArtikal briše artikal po ID-u
|
// ObrisiArtikal briše artikal po ID-u
|
||||||
func (h *Handler) ObrisiArtikal(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ObrisiArtikal(w http.ResponseWriter, r *http.Request) {
|
||||||
k := middleware.KorisnikIzKonteksta(r.Context())
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ type PodaciPodesavanja struct {
|
|||||||
LogoGreska string
|
LogoGreska string
|
||||||
BackupVracen bool
|
BackupVracen bool
|
||||||
Backupi []BackupInfo
|
Backupi []BackupInfo
|
||||||
|
BackupIntervalSati string
|
||||||
|
BackupBrojKopija string
|
||||||
LoginPozadina string
|
LoginPozadina string
|
||||||
LoginPozadinaOpacity string
|
LoginPozadinaOpacity string
|
||||||
LoginPozadinaBlurPozadine string
|
LoginPozadinaBlurPozadine string
|
||||||
@@ -87,6 +89,8 @@ func (h *Handler) Podesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||||
|
BackupIntervalSati: vrednostIliDefault(podesavanja, "backup_interval_sati", "24"),
|
||||||
|
BackupBrojKopija: vrednostIliDefault(podesavanja, "backup_broj_kopija", "7"),
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderujTemplate(w, "podesavanja", podaci)
|
h.renderujTemplate(w, "podesavanja", podaci)
|
||||||
@@ -244,6 +248,18 @@ func (h *Handler) SacuvajPodesavanja(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// backup podešavanja — čuvamo samo ako su validni pozitivni brojevi u razumnom opsegu
|
||||||
|
if v := r.FormValue("backup_interval_sati"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 720 {
|
||||||
|
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_interval_sati", strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := r.FormValue("backup_broj_kopija"); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n >= 1 && n <= 100 {
|
||||||
|
_ = ntechsqlite.SacuvajPodesavanje(r.Context(), h.DB, "backup_broj_kopija", strconv.Itoa(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sledeci := r.FormValue("_next")
|
sledeci := r.FormValue("_next")
|
||||||
if sledeci == "" || !strings.HasPrefix(sledeci, "/") {
|
if sledeci == "" || !strings.HasPrefix(sledeci, "/") {
|
||||||
sledeci = "/podesavanja"
|
sledeci = "/podesavanja"
|
||||||
@@ -591,6 +607,8 @@ func (h *Handler) napuniPodaciPodesavanja(r *http.Request, naslov string) (Podac
|
|||||||
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
LoginPozadinaBlurPozadine: vrednostIliDefault(podesavanja, "login_pozadina_blur_pozadine", "0"),
|
||||||
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
LoginPozadinaBlurKartice: vrednostIliDefault(podesavanja, "login_pozadina_blur_kartice", "12"),
|
||||||
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
LoginPozadinaZatamnjenjeKartice: vrednostIliDefault(podesavanja, "login_pozadina_zatamnjenje_kartice", "0"),
|
||||||
|
BackupIntervalSati: vrednostIliDefault(podesavanja, "backup_interval_sati", "24"),
|
||||||
|
BackupBrojKopija: vrednostIliDefault(podesavanja, "backup_broj_kopija", "7"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS {
|
|||||||
|
|
||||||
// Prodaja renderuje listu svih prodajnih naloga
|
// Prodaja renderuje listu svih prodajnih naloga
|
||||||
func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Prodaja(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -203,6 +208,11 @@ func (h *Handler) SacuvajProdaju(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
// DetaljiProdaje prikazuje pregled jednog prodajnog naloga sa svim stavkama
|
||||||
func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
@@ -255,6 +265,11 @@ func (h *Handler) DetaljiProdaje(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// StampaProdaje renderuje print-friendly stranicu za dati prodajni nalog
|
// StampaProdaje renderuje print-friendly stranicu za dati prodajni nalog
|
||||||
func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) StampaProdaje(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "prodaja.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled prodaje.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ type PodaciDetaljiNaloga struct {
|
|||||||
|
|
||||||
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
|
// Servis renderuje listu servisnih naloga sa opcionom pretragom i filterom statusa
|
||||||
func (h *Handler) Servis(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) Servis(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "servis.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled servisnih naloga.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError)
|
||||||
@@ -307,6 +312,11 @@ func (h *Handler) ObrisiNalog(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// DetaljiNaloga prikazuje sve podatke jednog servisnog naloga sa ugrađenim delovima
|
// DetaljiNaloga prikazuje sve podatke jednog servisnog naloga sa ugrađenim delovima
|
||||||
func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) DetaljiNaloga(w http.ResponseWriter, r *http.Request) {
|
||||||
|
k := middleware.KorisnikIzKonteksta(r.Context())
|
||||||
|
if !h.DozvoleRepo.ImaDozvolu(r.Context(), k.Uloga, "servis.pregled") {
|
||||||
|
http.Error(w, "Nemate dozvolu za pregled servisnih naloga.", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
http.Error(w, "Neispravan ID naloga", http.StatusBadRequest)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
// sve poznate akcije u sistemu
|
// sve poznate akcije u sistemu
|
||||||
|
// Napomena: pregled magacina i podsetnici su namerno javni (bez dozvole) i nisu ovde.
|
||||||
|
// Upravljanje korisnicima ide preko uloge (RequireAdmin middleware), ne preko dozvole.
|
||||||
var sveAkcije = []string{
|
var sveAkcije = []string{
|
||||||
"artikal.pregled",
|
|
||||||
"artikal.dodaj",
|
"artikal.dodaj",
|
||||||
"artikal.izmeni",
|
"artikal.izmeni",
|
||||||
"artikal.obrisi",
|
"artikal.obrisi",
|
||||||
"artikal.premesti",
|
"artikal.premesti",
|
||||||
"kategorija.pregled",
|
"kategorija.pregled",
|
||||||
"kategorija.dodaj",
|
"kategorija.dodaj",
|
||||||
"kategorija.izmeni",
|
|
||||||
"kategorija.obrisi",
|
"kategorija.obrisi",
|
||||||
"nabavka.pregled",
|
"nabavka.pregled",
|
||||||
"nabavka.dodaj",
|
"nabavka.dodaj",
|
||||||
@@ -25,30 +25,19 @@ var sveAkcije = []string{
|
|||||||
"prodaja.pregled",
|
"prodaja.pregled",
|
||||||
"prodaja.dodaj",
|
"prodaja.dodaj",
|
||||||
"prodaja.obrisi",
|
"prodaja.obrisi",
|
||||||
|
"prodaja.storno",
|
||||||
"klijent.pregled",
|
"klijent.pregled",
|
||||||
"klijent.dodaj",
|
"klijent.dodaj",
|
||||||
"klijent.izmeni",
|
"klijent.izmeni",
|
||||||
"klijent.obrisi",
|
"klijent.obrisi",
|
||||||
"podsetnik.pregled",
|
|
||||||
"podsetnik.dodaj",
|
|
||||||
"podsetnik.izmeni",
|
|
||||||
"podsetnik.obrisi",
|
|
||||||
"izvestaj.pregled",
|
"izvestaj.pregled",
|
||||||
"podesavanja.pregled",
|
"podesavanja.pregled",
|
||||||
"podesavanja.izmeni",
|
"podesavanja.izmeni",
|
||||||
"korisnik.pregled",
|
"podesavanja.login_pozadina",
|
||||||
"korisnik.dodaj",
|
|
||||||
"korisnik.izmeni",
|
|
||||||
"korisnik.obrisi",
|
|
||||||
"korisnik.uloga",
|
|
||||||
"backup.pregled",
|
"backup.pregled",
|
||||||
"backup.pokreni",
|
"backup.pokreni",
|
||||||
"podesavanja.login_pozadina",
|
|
||||||
"podesavanja.app_pozadina",
|
|
||||||
"tema.globalno",
|
|
||||||
"tema.lokalno",
|
"tema.lokalno",
|
||||||
"dashboard.prihod",
|
"dashboard.prihod",
|
||||||
"prodaja.storno",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SveAkcije vraća listu svih poznatih akcija — koristi se pri inicijalizaciji baze i resetu
|
// SveAkcije vraća listu svih poznatih akcija — koristi se pri inicijalizaciji baze i resetu
|
||||||
@@ -65,13 +54,12 @@ func ImaDozvolu(uloga, akcija string) bool {
|
|||||||
|
|
||||||
case "admin":
|
case "admin":
|
||||||
switch akcija {
|
switch akcija {
|
||||||
// artikal
|
// artikal (pregled magacina je javan — nije dozvola)
|
||||||
case "artikal.pregled", "artikal.dodaj", "artikal.izmeni",
|
case "artikal.dodaj", "artikal.izmeni",
|
||||||
"artikal.obrisi", "artikal.premesti":
|
"artikal.obrisi", "artikal.premesti":
|
||||||
return true
|
return true
|
||||||
// kategorija
|
// kategorija
|
||||||
case "kategorija.pregled", "kategorija.dodaj",
|
case "kategorija.pregled", "kategorija.dodaj", "kategorija.obrisi":
|
||||||
"kategorija.izmeni", "kategorija.obrisi":
|
|
||||||
return true
|
return true
|
||||||
// nabavka
|
// nabavka
|
||||||
case "nabavka.pregled", "nabavka.dodaj", "nabavka.obrisi":
|
case "nabavka.pregled", "nabavka.dodaj", "nabavka.obrisi":
|
||||||
@@ -91,26 +79,18 @@ func ImaDozvolu(uloga, akcija string) bool {
|
|||||||
case "klijent.pregled", "klijent.dodaj",
|
case "klijent.pregled", "klijent.dodaj",
|
||||||
"klijent.izmeni", "klijent.obrisi":
|
"klijent.izmeni", "klijent.obrisi":
|
||||||
return true
|
return true
|
||||||
// podsetnik
|
|
||||||
case "podsetnik.pregled", "podsetnik.dodaj",
|
|
||||||
"podsetnik.izmeni", "podsetnik.obrisi":
|
|
||||||
return true
|
|
||||||
// izveštaji i podešavanja
|
// izveštaji i podešavanja
|
||||||
case "izvestaj.pregled",
|
case "izvestaj.pregled",
|
||||||
"podesavanja.pregled", "podesavanja.izmeni":
|
"podesavanja.pregled", "podesavanja.izmeni":
|
||||||
return true
|
return true
|
||||||
// korisnici (bez promene uloge)
|
|
||||||
case "korisnik.pregled", "korisnik.dodaj",
|
|
||||||
"korisnik.izmeni", "korisnik.obrisi":
|
|
||||||
return true
|
|
||||||
// backup
|
// backup
|
||||||
case "backup.pregled", "backup.pokreni":
|
case "backup.pregled", "backup.pokreni":
|
||||||
return true
|
return true
|
||||||
// pozadinske slike
|
// pozadina prijavne stranice
|
||||||
case "podesavanja.login_pozadina", "podesavanja.app_pozadina":
|
case "podesavanja.login_pozadina":
|
||||||
return true
|
return true
|
||||||
// teme
|
// lokalna tema
|
||||||
case "tema.globalno", "tema.lokalno":
|
case "tema.lokalno":
|
||||||
return true
|
return true
|
||||||
// dashboard — prihod samo admin+
|
// dashboard — prihod samo admin+
|
||||||
case "dashboard.prihod":
|
case "dashboard.prihod":
|
||||||
@@ -120,9 +100,6 @@ func ImaDozvolu(uloga, akcija string) bool {
|
|||||||
|
|
||||||
case "radnik":
|
case "radnik":
|
||||||
switch akcija {
|
switch akcija {
|
||||||
// artikal — samo pregled
|
|
||||||
case "artikal.pregled":
|
|
||||||
return true
|
|
||||||
// kategorija — samo pregled
|
// kategorija — samo pregled
|
||||||
case "kategorija.pregled":
|
case "kategorija.pregled":
|
||||||
return true
|
return true
|
||||||
@@ -138,10 +115,6 @@ func ImaDozvolu(uloga, akcija string) bool {
|
|||||||
// klijent — bez brisanja
|
// klijent — bez brisanja
|
||||||
case "klijent.pregled", "klijent.dodaj", "klijent.izmeni":
|
case "klijent.pregled", "klijent.dodaj", "klijent.izmeni":
|
||||||
return true
|
return true
|
||||||
// podsetnik — sve
|
|
||||||
case "podsetnik.pregled", "podsetnik.dodaj",
|
|
||||||
"podsetnik.izmeni", "podsetnik.obrisi":
|
|
||||||
return true
|
|
||||||
// lokalna tema
|
// lokalna tema
|
||||||
case "tema.lokalno":
|
case "tema.lokalno":
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-- Podešavanja za automatski backup baze.
|
||||||
|
-- backup_interval_sati: na koliko sati se pravi automatska rezervna kopija (uz onu pri pokretanju).
|
||||||
|
-- backup_broj_kopija: koliko poslednjih kopija se čuva (starije se brišu — rotacija).
|
||||||
|
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('backup_interval_sati', '24');
|
||||||
|
INSERT OR IGNORE INTO podesavanja (kljuc, vrednost) VALUES ('backup_broj_kopija', '7');
|
||||||
@@ -219,10 +219,10 @@ body {
|
|||||||
.nav-podmeni.bez-tranzicije {
|
.nav-podmeni.bez-tranzicije {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
/* u skupljenom modu podmeni se nikad ne prikazuje */
|
/* u skupljenom modu otvoren podmeni dobija vizuelnu oznaku — pozadina + leva akcenat-linija */
|
||||||
.sidebar.skupljen .nav-podmeni {
|
.sidebar.skupljen .nav-podmeni.otvoren {
|
||||||
max-height: 0 !important;
|
background: var(--sb-aktivan);
|
||||||
transition: none;
|
border-left: 3px solid var(--sb-akcent);
|
||||||
}
|
}
|
||||||
.nav-podstavka {
|
.nav-podstavka {
|
||||||
padding-left: 48px !important;
|
padding-left: 48px !important;
|
||||||
|
|||||||
+19
-12
@@ -1,17 +1,25 @@
|
|||||||
// otvara/zatvara podmeni u sidebaru — funkcioniše i posle HTMX swap-a (čisti onclick, bez Alpine-a)
|
// otvara/zatvara podmeni u sidebaru — radi i kad je sidebar skupljen i kad je proširen
|
||||||
|
// (sidebar ostaje u zatečenom stanju). U isto vreme sme biti otvoren samo jedan podmeni.
|
||||||
function ntechTogglePodmeni(btn) {
|
function ntechTogglePodmeni(btn) {
|
||||||
var sidebar = document.getElementById('sidebar');
|
|
||||||
// ako je sidebar skupljen, raširimo ga pre nego otvorimo podmeni
|
|
||||||
if (sidebar && sidebar.classList.contains('skupljen')) {
|
|
||||||
sidebar.classList.remove('skupljen');
|
|
||||||
localStorage.setItem('sidebar-skupljeno', 'false');
|
|
||||||
}
|
|
||||||
var podmeni = btn.nextElementSibling;
|
var podmeni = btn.nextElementSibling;
|
||||||
if (!podmeni) return;
|
if (!podmeni) return;
|
||||||
podmeni.classList.toggle('otvoren');
|
var jeOtvoren = podmeni.classList.contains('otvoren');
|
||||||
var strelicaSvg = btn.querySelector('.nav-strelica svg');
|
|
||||||
if (strelicaSvg) {
|
// zatvori sve podmenije i vrati njihove strelice — međusobna isključivost
|
||||||
strelicaSvg.style.transform = podmeni.classList.contains('otvoren') ? 'rotate(180deg)' : 'rotate(0deg)';
|
document.querySelectorAll('#sidebar .nav-podmeni').forEach(function(el) {
|
||||||
|
el.classList.remove('otvoren');
|
||||||
|
var dugme = el.previousElementSibling;
|
||||||
|
if (dugme) {
|
||||||
|
var s = dugme.querySelector('.nav-strelica svg');
|
||||||
|
if (s) s.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ako kliknuti nije već bio otvoren — otvori ga
|
||||||
|
if (!jeOtvoren) {
|
||||||
|
podmeni.classList.add('otvoren');
|
||||||
|
var svg = btn.querySelector('.nav-strelica svg');
|
||||||
|
if (svg) svg.style.transform = 'rotate(180deg)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,5 +256,4 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
// za prvo učitavanje (defer script) — sprečava animaciju podmenia koji su inicijalno otvoreni
|
// za prvo učitavanje (defer script) — sprečava animaciju podmenia koji su inicijalno otvoreni
|
||||||
ntechInicijalizujPodmeni()
|
ntechInicijalizujPodmeni()
|
||||||
// dodaje klik listenere na podmeni dugmad (data-podmeni-dugme)
|
|
||||||
ntechDodajPodmeniListenere()
|
ntechDodajPodmeniListenere()
|
||||||
|
|||||||
@@ -47,23 +47,29 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if index .Dozvole "servis.pregled"}}
|
||||||
<a href="/servis" class="nav-stavka {{if eq .Stranica "servis"}}aktivan{{end}}">
|
<a href="/servis" class="nav-stavka {{if eq .Stranica "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>
|
<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>Servis</span>
|
||||||
<span class="nav-tooltip">Servis</span>
|
<span class="nav-tooltip">Servis</span>
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if index .Dozvole "prodaja.pregled"}}
|
||||||
<a href="/prodaja" class="nav-stavka {{if eq .Stranica "prodaja"}}aktivan{{end}}">
|
<a href="/prodaja" class="nav-stavka {{if eq .Stranica "prodaja"}}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"><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="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>
|
||||||
<span>Prodaja</span>
|
<span>Prodaja</span>
|
||||||
<span class="nav-tooltip">Prodaja</span>
|
<span class="nav-tooltip">Prodaja</span>
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if index .Dozvole "klijent.pregled"}}
|
||||||
<a href="/klijenti" class="nav-stavka {{if eq .Stranica "klijenti"}}aktivan{{end}}">
|
<a href="/klijenti" class="nav-stavka {{if eq .Stranica "klijenti"}}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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></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="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
<span>Klijenti</span>
|
<span>Klijenti</span>
|
||||||
<span class="nav-tooltip">Klijenti</span>
|
<span class="nav-tooltip">Klijenti</span>
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<a href="/podsetnici" class="nav-stavka {{if eq .Stranica "podsetnici"}}aktivan{{end}}">
|
<a href="/podsetnici" class="nav-stavka {{if eq .Stranica "podsetnici"}}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="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="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>
|
||||||
@@ -71,11 +77,13 @@
|
|||||||
<span class="nav-tooltip">Podsetnici</span>
|
<span class="nav-tooltip">Podsetnici</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
{{if index .Dozvole "dobavljac.pregled"}}
|
||||||
<a href="/dobavljaci" class="nav-stavka {{if eq .Stranica "dobavljaci"}}aktivan{{end}}">
|
<a href="/dobavljaci" class="nav-stavka {{if eq .Stranica "dobavljaci"}}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"><rect x="1" y="3" width="15" height="13" rx="1"/><path d="M16 8h4l3 3v5h-7V8z"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></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"><rect x="1" y="3" width="15" height="13" rx="1"/><path d="M16 8h4l3 3v5h-7V8z"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
||||||
<span>Dobavljači</span>
|
<span>Dobavljači</span>
|
||||||
<span class="nav-tooltip">Dobavljači</span>
|
<span class="nav-tooltip">Dobavljači</span>
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if index .Dozvole "izvestaj.pregled"}}
|
{{if index .Dozvole "izvestaj.pregled"}}
|
||||||
<a href="/izvestaji" class="nav-stavka {{if eq .Stranica "izvestaji"}}aktivan{{end}}">
|
<a href="/izvestaji" class="nav-stavka {{if eq .Stranica "izvestaji"}}aktivan{{end}}">
|
||||||
@@ -105,7 +113,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="nav-podmeni {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}otvoren{{end}}">
|
<div class="nav-podmeni {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}otvoren{{end}}">
|
||||||
<a href="/admin/profil" class="nav-stavka nav-podstavka {{if eq .Stranica "profil"}}aktivan{{end}}">
|
<a href="/admin/profil" class="nav-stavka nav-podstavka {{if eq .Stranica "profil"}}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="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></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"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M15 8h2"/><path d="M15 12h2"/><path d="M7 16h10"/></svg>
|
||||||
<span>Opšte</span>
|
<span>Opšte</span>
|
||||||
<span class="nav-tooltip">Opšte</span>
|
<span class="nav-tooltip">Opšte</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -188,26 +196,4 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
var sidebar = document.getElementById('sidebar');
|
|
||||||
if (!sidebar) return;
|
|
||||||
sidebar.querySelectorAll('[data-podmeni-dugme]').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (sidebar.classList.contains('skupljen')) {
|
|
||||||
sidebar.classList.remove('skupljen');
|
|
||||||
localStorage.setItem('sidebar-skupljen', 'false');
|
|
||||||
}
|
|
||||||
var podmeni = btn.nextElementSibling;
|
|
||||||
if (!podmeni) return;
|
|
||||||
podmeni.classList.toggle('otvoren');
|
|
||||||
var svg = btn.querySelector('.nav-strelica svg');
|
|
||||||
if (svg) {
|
|
||||||
svg.style.transform = podmeni.classList.contains('otvoren') ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -40,14 +40,17 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
<!-- Magacin -->
|
<!-- Dashboard -->
|
||||||
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Magacin</td></tr>
|
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Dashboard</td></tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled artikala</td>
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Prikaz prihoda</td>
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.pregled" {{if index .DozvoleRadnik "artikal.pregled"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__dashboard.prihod" {{if index .DozvoleRadnik "dashboard.prihod"}}checked{{end}}></td>
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__artikal.pregled" {{if index .DozvoleAdmin "artikal.pregled"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__dashboard.prihod" {{if index .DozvoleAdmin "dashboard.prihod"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<!-- Magacin -->
|
||||||
|
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Magacin</td></tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje artikala</td>
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje artikala</td>
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.dodaj" {{if index .DozvoleRadnik "artikal.dodaj"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__artikal.dodaj" {{if index .DozvoleRadnik "artikal.dodaj"}}checked{{end}}></td>
|
||||||
@@ -86,12 +89,6 @@
|
|||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.dodaj" {{if index .DozvoleRadnik "kategorija.dodaj"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.dodaj" {{if index .DozvoleRadnik "kategorija.dodaj"}}checked{{end}}></td>
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.dodaj" {{if index .DozvoleAdmin "kategorija.dodaj"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.dodaj" {{if index .DozvoleAdmin "kategorija.dodaj"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena kategorija</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__kategorija.izmeni" {{if index .DozvoleRadnik "kategorija.izmeni"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__kategorija.izmeni" {{if index .DozvoleAdmin "kategorija.izmeni"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje kategorija</td>
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje kategorija</td>
|
||||||
@@ -194,6 +191,12 @@
|
|||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.obrisi" {{if index .DozvoleRadnik "prodaja.obrisi"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.obrisi" {{if index .DozvoleRadnik "prodaja.obrisi"}}checked{{end}}></td>
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.obrisi" {{if index .DozvoleAdmin "prodaja.obrisi"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.obrisi" {{if index .DozvoleAdmin "prodaja.obrisi"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Storniranje prodaje</td>
|
||||||
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__prodaja.storno" {{if index .DozvoleRadnik "prodaja.storno"}}checked{{end}}></td>
|
||||||
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__prodaja.storno" {{if index .DozvoleAdmin "prodaja.storno"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Klijenti -->
|
<!-- Klijenti -->
|
||||||
@@ -223,33 +226,6 @@
|
|||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Podsetnici -->
|
|
||||||
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Podsetnici</td></tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled podsetnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.pregled" {{if index .DozvoleRadnik "podsetnik.pregled"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.pregled" {{if index .DozvoleAdmin "podsetnik.pregled"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje podsetnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.dodaj" {{if index .DozvoleRadnik "podsetnik.dodaj"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.dodaj" {{if index .DozvoleAdmin "podsetnik.dodaj"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena podsetnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.izmeni" {{if index .DozvoleRadnik "podsetnik.izmeni"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.izmeni" {{if index .DozvoleAdmin "podsetnik.izmeni"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje podsetnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podsetnik.obrisi" {{if index .DozvoleRadnik "podsetnik.obrisi"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podsetnik.obrisi" {{if index .DozvoleAdmin "podsetnik.obrisi"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Izveštaji -->
|
<!-- Izveštaji -->
|
||||||
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Izveštaji</td></tr>
|
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Izveštaji</td></tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
@@ -273,37 +249,19 @@
|
|||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.izmeni" {{if index .DozvoleAdmin "podesavanja.izmeni"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.izmeni" {{if index .DozvoleAdmin "podesavanja.izmeni"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Korisnici -->
|
|
||||||
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Korisnici</td></tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Pregled korisnika</td>
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena pozadine prijavne stranice</td>
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.pregled" {{if index .DozvoleRadnik "korisnik.pregled"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__podesavanja.login_pozadina" {{if index .DozvoleRadnik "podesavanja.login_pozadina"}}checked{{end}}></td>
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.pregled" {{if index .DozvoleAdmin "korisnik.pregled"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__podesavanja.login_pozadina" {{if index .DozvoleAdmin "podesavanja.login_pozadina"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Dodavanje korisnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.dodaj" {{if index .DozvoleRadnik "korisnik.dodaj"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.dodaj" {{if index .DozvoleAdmin "korisnik.dodaj"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
<!-- Tema -->
|
||||||
|
<tr class="matrica-modul"><td {{if eq .KorisnikUloga "superadmin"}}colspan="3"{{else}}colspan="2"{{end}}>Tema</td></tr>
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
<tr style="border-bottom:0.5px solid var(--ivica);">
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Izmena korisnika</td>
|
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Lična tema naloga</td>
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.izmeni" {{if index .DozvoleRadnik "korisnik.izmeni"}}checked{{end}}></td>
|
<td class="matrica-checkbox"><input type="checkbox" name="radnik__tema.lokalno" {{if index .DozvoleRadnik "tema.lokalno"}}checked{{end}}></td>
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.izmeni" {{if index .DozvoleAdmin "korisnik.izmeni"}}checked{{end}}></td>{{end}}
|
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__tema.lokalno" {{if index .DozvoleAdmin "tema.lokalno"}}checked{{end}}></td>{{end}}
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Brisanje korisnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.obrisi" {{if index .DozvoleRadnik "korisnik.obrisi"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.obrisi" {{if index .DozvoleAdmin "korisnik.obrisi"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr style="border-bottom:0.5px solid var(--ivica);">
|
|
||||||
<td style="padding:9px 16px;font-size:13px;color:var(--tekst-glavni);">Promena uloge korisnika</td>
|
|
||||||
<td class="matrica-checkbox"><input type="checkbox" name="radnik__korisnik.uloga" {{if index .DozvoleRadnik "korisnik.uloga"}}checked{{end}}></td>
|
|
||||||
{{if eq .KorisnikUloga "superadmin"}}<td class="matrica-checkbox"><input type="checkbox" name="admin__korisnik.uloga" {{if index .DozvoleAdmin "korisnik.uloga"}}checked{{end}}></td>{{end}}
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,34 @@
|
|||||||
.magacin-kartica:nth-child(3) { animation-delay: 0.16s; }
|
.magacin-kartica:nth-child(3) { animation-delay: 0.16s; }
|
||||||
.magacin-kartica:nth-child(4) { animation-delay: 0.22s; }
|
.magacin-kartica:nth-child(4) { animation-delay: 0.22s; }
|
||||||
.magacin-kartica:nth-child(5) { animation-delay: 0.28s; }
|
.magacin-kartica:nth-child(5) { animation-delay: 0.28s; }
|
||||||
|
|
||||||
|
/* padajući meni za premeštanje artikla u drugu kategoriju */
|
||||||
|
.premesti-meni summary { list-style: none; }
|
||||||
|
.premesti-meni summary::-webkit-details-marker { display: none; }
|
||||||
|
.premesti-meni .premesti-panel {
|
||||||
|
margin-top: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
background: var(--pozadina);
|
||||||
|
border: 0.5px solid var(--ivica);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
.premesti-opcija {
|
||||||
|
text-align: left;
|
||||||
|
padding: 7px 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--tekst-glavni);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.premesti-opcija:hover { background: var(--pozadina-hover); }
|
||||||
|
.premesti-opcija-prazno { color: var(--tekst-sporedni); border-top: 0.5px solid var(--ivica); }
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
@@ -31,6 +59,9 @@
|
|||||||
{{if .Obrisan}}
|
{{if .Obrisan}}
|
||||||
<div class="poruka-uspeh">Artikal je uspešno obrisan.</div>
|
<div class="poruka-uspeh">Artikal je uspešno obrisan.</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if .Premesten}}
|
||||||
|
<div class="poruka-uspeh">Artikal je premešten u drugu kategoriju.</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- dugmad -->
|
<!-- dugmad -->
|
||||||
<div style="display:flex;gap:10px;flex-wrap:wrap;">
|
<div style="display:flex;gap:10px;flex-wrap:wrap;">
|
||||||
@@ -101,6 +132,17 @@
|
|||||||
Izmeni
|
Izmeni
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
||||||
|
<details class="premesti-meni" style="align-self:center;">
|
||||||
|
<summary class="btn-primarno-malo" style="cursor:pointer;">Premesti</summary>
|
||||||
|
<form method="POST" action="/magacin/premesti/{{.ID}}" class="premesti-panel">
|
||||||
|
{{range $.Kategorije}}
|
||||||
|
<button type="submit" name="kategorija_id" value="{{.ID}}" class="premesti-opcija">{{.Naziv}}</button>
|
||||||
|
{{end}}
|
||||||
|
<button type="submit" name="kategorija_id" value="" class="premesti-opcija premesti-opcija-prazno">Bez kategorije</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
{{end}}{{end}}
|
||||||
{{if index $.Dozvole "artikal.obrisi"}}
|
{{if index $.Dozvole "artikal.obrisi"}}
|
||||||
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
||||||
data-potvrda="Da li ste sigurni da želite da obrišete ovaj artikal?">
|
data-potvrda="Da li ste sigurni da želite da obrišete ovaj artikal?">
|
||||||
@@ -133,10 +175,21 @@
|
|||||||
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">{{.KategorijaNaziv}}</div>
|
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:2px;">{{.KategorijaNaziv}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px;flex-shrink:0;">
|
<div style="display:flex;gap:8px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;">
|
||||||
{{if index $.Dozvole "artikal.izmeni"}}
|
{{if index $.Dozvole "artikal.izmeni"}}
|
||||||
<a href="/magacin/izmeni/{{.ID}}" class="btn-primarno-malo">Izmeni</a>
|
<a href="/magacin/izmeni/{{.ID}}" class="btn-primarno-malo">Izmeni</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if index $.Dozvole "artikal.premesti"}}{{if $.Kategorije}}
|
||||||
|
<details class="premesti-meni">
|
||||||
|
<summary class="btn-primarno-malo" style="cursor:pointer;">Premesti</summary>
|
||||||
|
<form method="POST" action="/magacin/premesti/{{.ID}}" class="premesti-panel">
|
||||||
|
{{range $.Kategorije}}
|
||||||
|
<button type="submit" name="kategorija_id" value="{{.ID}}" class="premesti-opcija">{{.Naziv}}</button>
|
||||||
|
{{end}}
|
||||||
|
<button type="submit" name="kategorija_id" value="" class="premesti-opcija premesti-opcija-prazno">Bez kategorije</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
{{end}}{{end}}
|
||||||
{{if index $.Dozvole "artikal.obrisi"}}
|
{{if index $.Dozvole "artikal.obrisi"}}
|
||||||
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
<a href="/magacin/obrisi/{{.ID}}" class="btn-obrisi-malo"
|
||||||
data-potvrda="Da li ste sigurni da želite da obrišete ovaj artikal?">
|
data-potvrda="Da li ste sigurni da želite da obrišete ovaj artikal?">
|
||||||
|
|||||||
@@ -35,6 +35,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- automatski backup — interval i broj kopija -->
|
||||||
|
<div style="margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
|
||||||
|
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Automatski backup</div>
|
||||||
|
<form method="POST" action="/podesavanja/sacuvaj">
|
||||||
|
<input type="hidden" name="_next" value="/admin/podesavanja/sistem">
|
||||||
|
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;">
|
||||||
|
<div>
|
||||||
|
<label for="backup_interval_sati" style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Razmak između backupa (sati)</label>
|
||||||
|
<input type="number" id="backup_interval_sati" name="backup_interval_sati" min="1" max="720" value="{{.BackupIntervalSati}}"
|
||||||
|
style="width:140px;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="backup_broj_kopija" style="font-size:13px;color:var(--tekst-sporedni);display:block;margin-bottom:6px;">Broj kopija koje se čuvaju</label>
|
||||||
|
<input type="number" id="backup_broj_kopija" name="backup_broj_kopija" min="1" max="100" value="{{.BackupBrojKopija}}"
|
||||||
|
style="width:140px;padding:8px 12px;background:var(--pozadina);border:0.5px solid var(--ivica);border-radius:8px;color:var(--tekst-glavni);font-size:14px;">
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
style="padding:9px 18px;background:var(--sb-akcent);border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:500;cursor:pointer;">
|
||||||
|
Sačuvaj
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:8px;">
|
||||||
|
Backup se pravi pri pokretanju programa i zatim na svakih {{.BackupIntervalSati}} h. Najstarije kopije se brišu kad ih ima više od zadatog broja.
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- panel sa listom backupa -->
|
<!-- panel sa listom backupa -->
|
||||||
<div id="backup-panel" style="display:none;margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
|
<div id="backup-panel" style="display:none;margin-top:16px;border-top:0.5px solid var(--ivica);padding-top:16px;">
|
||||||
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Dostupne rezervne kopije</div>
|
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);margin-bottom:10px;">Dostupne rezervne kopije</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user