diff --git a/Readme.md b/Readme.md index c94e23e..560f6b8 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # NTech -![Go Version](https://img.shields.io/badge/go-1.26-blue) +![Go Version](https://img.shields.io/badge/go-1.24-blue) ![License](https://img.shields.io/badge/license-MIT-green) Poslovna aplikacija za upravljanje servisom računara, magacinom delova i prodajom. Napravljena u Go-u, radi u brauzeru, ne zahteva internet vezu ni eksterne servise. @@ -23,22 +23,27 @@ Cilj je jednostavan: sve što servis treba da prati nalazi se na jednom mestu, b - Inicijalno podešavanje pri prvom pokretanju (setup wizard) - Sistem migracija baze podataka -- Osnovna struktura baze: artikli, kategorije, klijenti, dobavljači, servisni nalozi, prodajni nalozi, podsetnici - -### U razvoju - -- Korisničko sučelje — sidebar navigacija, sistem tema, dashboard -- Magacin — praćenje stanja delova, lokacija, količina -- Servisni nalozi — prijem, dijagnostika, statusna traka, cene -- Prodajni nalozi — stavke, obračun, evidencija kupaca -- Klijenti i dobavljači — baza kontakata, istorija +- Korisnički interfejs — sidebar navigacija, sistem tema (tamna/svetla), dashboard sa statistikama +- Prijava korisnika — sesije na serveru, zaključavanje naloga +- Dvofaktorska autentifikacija (TOTP) — aktivacija sa QR kodom, rezervni kodovi +- Bruteforce zaštita — IP zaključavanje nakon 5 neuspelih pokušaja u 15 minuta +- CSRF zaštita — double-submit cookie pattern, automatska injekcija tokena u sve forme +- Bezbednosni HTTP headeri (CSP, X-Frame-Options, Referrer-Policy, nosniff...) +- Evidencija pokušaja prijave — istorija po korisniku, IP, razlog, datum +- Korisnici i uloge — admin panel, upravljanje korisnicima +- Magacin — artikli, kategorije, filtriranje, kritični nivoi zaliha +- Servisni nalozi — prijem, statusna traka, troškovi, priznanica +- Prodajni nalozi — stavke, obračun, priznanica sa podacima firme i klijenta +- Nabavke — evidencija nabavki od dobavljača +- Klijenti i dobavljači — baza kontakata +- Podsetnici — evidencija sa rokom +- Izveštaji — pregled prihoda, stanje magacina +- Podešavanja — naziv, adresa, PIB, logo firme; promena teme ### Planirano -- Prijava korisnika — sesije, zaključavanje naloga -- Dvofaktorska autentifikacija (TOTP) -- Izveštaji — prodaja, stanje magacina, prihodi - Podrška za PostgreSQL (za višekorisničko okruženje) +- WebAuthn / Passkey prijava (šema baze je pripremljena) - Obaveštenja (e-pošta / WhatsApp) — odloženo za kasniju fazu - Skeniranje barkodova putem kamere — odloženo za kasniju fazu @@ -46,16 +51,14 @@ Cilj je jednostavan: sve što servis treba da prati nalazi se na jednom mestu, b ## Tehnologije -| Tehnologija | Uloga | -| ------------------------------------------------------------------------------------ | -------------------------------- | -| [Go](https://go.dev) | backend jezik | -| [chi](https://github.com/go-chi/chi) | HTTP ruter | -| [html/template](https://pkg.go.dev/html/template) | serverski šabloni | -| [HTMX](https://htmx.org) | interaktivnost bez build procesa | -| [TailwindCSS](https://tailwindcss.com) | stilizovanje | -| [Alpine.js](https://alpinejs.dev) | UI logika na strani klijenta | -| [SQLite](https://sqlite.org) + [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | glavna baza (čisti Go, bez CGO) | -| [PostgreSQL](https://www.postgresql.org) + [pgx/v5](https://github.com/jackc/pgx) | opciona baza za produkciju | +| Tehnologija | Uloga | +| ------------------------------------------------------------------------------------ | ------------------------------- | +| [Go](https://go.dev) | backend jezik | +| [chi](https://github.com/go-chi/chi) | HTTP ruter | +| [html/template](https://pkg.go.dev/html/template) | serverski šabloni | +| [Alpine.js](https://alpinejs.dev) | UI logika na strani klijenta | +| [SQLite](https://sqlite.org) + [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | glavna baza (čisti Go, bez CGO) | +| [PostgreSQL](https://www.postgresql.org) + [pgx/v5](https://github.com/jackc/pgx) | opciona baza za produkciju | --- @@ -73,22 +76,29 @@ Cilj je jednostavan: sve što servis treba da prati nalazi se na jednom mestu, b git clone cd GoNtech -# 2. Kopiranje i popunjavanje .env fajla -cp .env.example .env -# Otvori .env i postavi vrednosti (videti tabelu ispod) +# 2. Kopiranje konfiguracionog fajla +cp ntech.env.example ntech.env +# Otvori ntech.env i postavi vrednosti (videti tabelu ispod) -# 3. Pokretanje u razvojnom okruženju +# 3. Učitavanje promenljivih i pokretanje u razvojnom okruženju +export $(grep -v '^#' ntech.env | xargs) go run ./cmd/ntech ``` -Program se otvara na `http://localhost:8080`. +Program se otvara na `http://localhost:8080` (ili na portu definisanom u `ntech.env`). Pri prvom pokretanju automatski se pokreće setup wizard. ### Produkcioni build ```bash -CGO_ENABLED=0 go build -o ntech ./cmd/ntech +# Pomoću build.sh skripte (prima opcioni argument verzije) +./build.sh 1.0.0 + +# Ili ručno +CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ + -ldflags "-X main.Verzija=1.0.0 -s -w" \ + -o ntech ./cmd/ntech ./ntech ``` @@ -98,7 +108,7 @@ Rezultat je jedan statički binarni fajl bez zavisnosti. ## Promenljive okruženja -Kopirati `.env.example` u `.env` i popuniti vrednosti. Fajl `.env` se **ne commituje** u Git. +Kopirati `ntech.env.example` u `ntech.env` i popuniti vrednosti. Fajl `ntech.env` se **ne commituje** u Git. | Promenljiva | Podrazumevano | Opis | | -------------- | ------------- | -------------------------------------------- | @@ -118,19 +128,21 @@ ntech/ ├── cmd/ │ └── ntech/ # ulazna tačka programa ├── internal/ -│ ├── auth/ # prijava, sesije, 2FA +│ ├── auth/ # prijava, sesije, fail2ban log │ ├── config/ # podešavanja, setup wizard │ ├── db/ # sloj baze podataka -│ │ ├── sqlite/ # SQLite implementacija -│ │ └── postgres/ # PostgreSQL implementacija +│ │ └── sqlite/ # SQLite implementacija │ ├── handler/ # HTTP handleri -│ ├── middleware/ # autentifikacija, logovanje, ograničavanje zahteva +│ ├── middleware/ # CSRF, bezbednost headeri, autentifikacija │ └── model/ # zajednički tipovi podataka ├── web/ -│ ├── static/ # CSS, JavaScript, slike +│ ├── static/ # CSS, JavaScript, slike, logotipi │ └── templates/ # HTML šabloni ├── migrations/ # SQL migracije (001_opis.sql, 002_opis.sql, ...) -├── .env.example # primer konfiguracije +├── logs/ # auth.log i ostali logovi +├── backups/ # rezervne kopije baze +├── build.sh # skripta za produkcioni build +├── ntech.env # lokalna konfiguracija (ne commituje se) ├── go.mod └── go.sum ``` diff --git a/internal/handler/admin.go b/internal/handler/admin.go index d7d2357..aa27cb1 100644 --- a/internal/handler/admin.go +++ b/internal/handler/admin.go @@ -192,7 +192,7 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) { podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) ps := h.popuniPodaciStranice(r, podesavanja) - ps.Stranica = "admin" + ps.Stranica = "profil" ps.NaslovStranice = "Moj profil" podaci := podaciAdminProfil{ @@ -261,7 +261,7 @@ func (h *Handler) AdminTotpPokreni(w http.ResponseWriter, r *http.Request) { } ps := h.popuniPodaciStranice(r, podesavanja) - ps.Stranica = "admin" + ps.Stranica = "profil" ps.NaslovStranice = "Podesi 2FA" podaci := podaciAdminProfil{ @@ -294,7 +294,7 @@ func (h *Handler) AdminTotpAktivacija(w http.ResponseWriter, r *http.Request) { // ponovni prikaz sa greškom — regenerišemo isti tajnu podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) ps := h.popuniPodaciStranice(r, podesavanja) - ps.Stranica = "admin" + ps.Stranica = "profil" ps.NaslovStranice = "Podesi 2FA" // regenerišemo QR za već generisanu tajnu (korisnik je video ovaj QR) diff --git a/internal/handler/dashboard.go b/internal/handler/dashboard.go index f09dbf8..8a55743 100644 --- a/internal/handler/dashboard.go +++ b/internal/handler/dashboard.go @@ -30,7 +30,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { if err := h.DB.QueryRowContext(ctx, ` SELECT COUNT(*) FROM servisni_nalozi - WHERE status NOT IN ('Završeno', 'Preuzeto')`, + WHERE status != 'Završeno'`, ).Scan(&aktivniServisi); err != nil { log.Printf("dashboard: aktivni servisi: %v", err) } diff --git a/internal/handler/dobavljac.go b/internal/handler/dobavljac.go index 6528576..fa25e2a 100644 --- a/internal/handler/dobavljac.go +++ b/internal/handler/dobavljac.go @@ -43,21 +43,15 @@ func (h *Handler) Dobavljaci(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "dobavljaci" + ps.NaslovStranice = "Dobavljači" podaci := PodaciDobavljaca{ - PodaciStranice: model.PodaciStranice{ - Stranica: "dobavljaci", - NaslovStranice: "Dobavljači", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Dobavljaci: dobavljaci, - Pretraga: pretraga, - Sacuvano: r.URL.Query().Get("sacuvano") == "1", - Obrisan: r.URL.Query().Get("obrisan") == "1", + PodaciStranice: ps, + Dobavljaci: dobavljaci, + Pretraga: pretraga, + Sacuvano: r.URL.Query().Get("sacuvano") == "1", + Obrisan: r.URL.Query().Get("obrisan") == "1", } h.renderujTemplate(w, "dobavljaci", podaci) @@ -71,18 +65,12 @@ func (h *Handler) NoviDobavljac(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "dobavljaci" + ps.NaslovStranice = "Novi dobavljač" h.renderujFormuDobavljaca(w, PodaciFormeDobavljaca{ - PodaciStranice: model.PodaciStranice{ - Stranica: "dobavljaci", - NaslovStranice: "Novi dobavljač", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Izmena: false, + PodaciStranice: ps, + Izmena: false, }) } @@ -96,20 +84,14 @@ func (h *Handler) SacuvajDobavljaca(w http.ResponseWriter, r *http.Request) { dobavljac, greska := parseFormuDobavljaca(r) if greska != "" { podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "dobavljaci" + ps.NaslovStranice = "Novi dobavljač" h.renderujFormuDobavljaca(w, PodaciFormeDobavljaca{ - PodaciStranice: model.PodaciStranice{ - Stranica: "dobavljaci", - NaslovStranice: "Novi dobavljač", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Dobavljac: dobavljac, - Greska: greska, - Izmena: false, + PodaciStranice: ps, + Dobavljac: dobavljac, + Greska: greska, + Izmena: false, }) return } @@ -142,19 +124,13 @@ func (h *Handler) IzmeniDobavljaca(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "dobavljaci" + ps.NaslovStranice = "Izmeni dobavljača" h.renderujFormuDobavljaca(w, PodaciFormeDobavljaca{ - PodaciStranice: model.PodaciStranice{ - Stranica: "dobavljaci", - NaslovStranice: "Izmeni dobavljača", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Dobavljac: *dobavljac, - Izmena: true, + PodaciStranice: ps, + Dobavljac: *dobavljac, + Izmena: true, }) } @@ -175,20 +151,14 @@ func (h *Handler) SacuvajIzmeneDobavljaca(w http.ResponseWriter, r *http.Request if greska != "" { podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) dobavljac.ID = id + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "dobavljaci" + ps.NaslovStranice = "Izmeni dobavljača" h.renderujFormuDobavljaca(w, PodaciFormeDobavljaca{ - PodaciStranice: model.PodaciStranice{ - Stranica: "dobavljaci", - NaslovStranice: "Izmeni dobavljača", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Dobavljac: dobavljac, - Greska: greska, - Izmena: true, + PodaciStranice: ps, + Dobavljac: dobavljac, + Greska: greska, + Izmena: true, }) return } diff --git a/internal/handler/kategorija.go b/internal/handler/kategorija.go index 6d9a0c2..d1ff218 100644 --- a/internal/handler/kategorija.go +++ b/internal/handler/kategorija.go @@ -32,20 +32,14 @@ func (h *Handler) Kategorije(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Kategorije" podaci := PodaciKategorija{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Kategorije", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Kategorije: kategorije, - Sacuvano: r.URL.Query().Get("sacuvano") == "1", - Obrisana: r.URL.Query().Get("obrisana") == "1", + PodaciStranice: ps, + Kategorije: kategorije, + Sacuvano: r.URL.Query().Get("sacuvano") == "1", + Obrisana: r.URL.Query().Get("obrisana") == "1", } h.renderujTemplate(w, "kategorije", podaci) diff --git a/internal/handler/klijent.go b/internal/handler/klijent.go index c25aec5..68873bd 100644 --- a/internal/handler/klijent.go +++ b/internal/handler/klijent.go @@ -44,21 +44,15 @@ func (h *Handler) Klijenti(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Klijenti" podaci := PodaciKlijenata{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Klijenti", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijenti: klijenti, - Pretraga: pretraga, - Sacuvano: r.URL.Query().Get("sacuvano") == "1", - Obrisan: r.URL.Query().Get("obrisan") == "1", + PodaciStranice: ps, + Klijenti: klijenti, + Pretraga: pretraga, + Sacuvano: r.URL.Query().Get("sacuvano") == "1", + Obrisan: r.URL.Query().Get("obrisan") == "1", } h.renderujTemplate(w, "klijenti", podaci) @@ -72,18 +66,12 @@ func (h *Handler) NoviKlijent(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Novi klijent" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Novi klijent", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Izmena: false, + PodaciStranice: ps, + Izmena: false, }) } @@ -97,20 +85,14 @@ func (h *Handler) SacuvajKlijenta(w http.ResponseWriter, r *http.Request) { klijent, greska := parseFormuKlijenta(r) if greska != "" { podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Novi klijent" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Novi klijent", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijent: klijent, - Greska: greska, - Izmena: false, + PodaciStranice: ps, + Klijent: klijent, + Greska: greska, + Izmena: false, }) return } @@ -118,20 +100,14 @@ func (h *Handler) SacuvajKlijenta(w http.ResponseWriter, r *http.Request) { if _, err := h.KlijentiRepo.Kreiraj(r.Context(), &klijent); err != nil { log.Printf("greška pri čuvanju klijenta: %v", err) podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Novi klijent" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Novi klijent", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijent: klijent, - Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.", - Izmena: false, + PodaciStranice: ps, + Klijent: klijent, + Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.", + Izmena: false, }) return } @@ -159,19 +135,13 @@ func (h *Handler) IzmeniKlijenta(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Izmeni klijenta" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Izmeni klijenta", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijent: *klijent, - Izmena: true, + PodaciStranice: ps, + Klijent: *klijent, + Izmena: true, }) } @@ -192,20 +162,14 @@ func (h *Handler) SacuvajIzmenuKlijenta(w http.ResponseWriter, r *http.Request) if greska != "" { podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) klijent.ID = id + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Izmeni klijenta" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Izmeni klijenta", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijent: klijent, - Greska: greska, - Izmena: true, + PodaciStranice: ps, + Klijent: klijent, + Greska: greska, + Izmena: true, }) return } @@ -214,20 +178,14 @@ func (h *Handler) SacuvajIzmenuKlijenta(w http.ResponseWriter, r *http.Request) if err := h.KlijentiRepo.Izmeni(r.Context(), &klijent); err != nil { log.Printf("greška pri čuvanju izmene klijenta: %v", err) podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "klijenti" + ps.NaslovStranice = "Izmeni klijenta" h.renderujFormuKlijenta(w, PodaciFormeKlijenta{ - PodaciStranice: model.PodaciStranice{ - Stranica: "klijenti", - NaslovStranice: "Izmeni klijenta", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Klijent: klijent, - Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.", - Izmena: true, + PodaciStranice: ps, + Klijent: klijent, + Greska: "Došlo je do greške pri čuvanju. Pokušajte ponovo.", + Izmena: true, }) return } diff --git a/internal/handler/magacin.go b/internal/handler/magacin.go index 1afc4c4..4245e67 100644 --- a/internal/handler/magacin.go +++ b/internal/handler/magacin.go @@ -56,17 +56,11 @@ func (h *Handler) Magacin(w http.ResponseWriter, r *http.Request) { return } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Magacin" podaci := PodaciMagacina{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Magacin", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, + PodaciStranice: ps, Artikli: artikli, Kategorije: kategorije, Filter: filter, diff --git a/internal/handler/magacin_forma.go b/internal/handler/magacin_forma.go index f72c2da..861d34c 100644 --- a/internal/handler/magacin_forma.go +++ b/internal/handler/magacin_forma.go @@ -35,22 +35,14 @@ func (h *Handler) NoviArtikal(w http.ResponseWriter, r *http.Request) { return } - podaci := PodaciFormeArtikla{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Novi artikal", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, - Kategorije: kategorije, - Izmena: false, - } - - h.renderujFormuArtikla(w, podaci) + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Novi artikal" + h.renderujFormuArtikla(w, PodaciFormeArtikla{ + PodaciStranice: ps, + Kategorije: kategorije, + Izmena: false, + }) } // SacuvajArtikal prima POST formu i čuva novi artikal @@ -68,17 +60,11 @@ func (h *Handler) SacuvajArtikal(w http.ResponseWriter, r *http.Request) { if artikal.KategorijaID != nil { katIDStr = strconv.FormatInt(*artikal.KategorijaID, 10) } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Novi artikal" h.renderujFormuArtikla(w, PodaciFormeArtikla{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Novi artikal", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, + PodaciStranice: ps, Artikal: artikal, Kategorije: kategorije, KategorijaIDStr: katIDStr, @@ -136,24 +122,16 @@ func (h *Handler) IzmeniArtikal(w http.ResponseWriter, r *http.Request) { katIDStr = strconv.FormatInt(*artikal.KategorijaID, 10) } - podaci := PodaciFormeArtikla{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Izmeni artikal", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Izmeni artikal" + h.renderujFormuArtikla(w, PodaciFormeArtikla{ + PodaciStranice: ps, Artikal: *artikal, Kategorije: kategorije, KategorijaIDStr: katIDStr, Izmena: true, - } - - h.renderujFormuArtikla(w, podaci) + }) } // SacuvajIzmenuArtikla prima POST formu i čuva izmenu artikla @@ -179,17 +157,11 @@ func (h *Handler) SacuvajIzmenuArtikla(w http.ResponseWriter, r *http.Request) { if artikal.KategorijaID != nil { katIDStr = strconv.FormatInt(*artikal.KategorijaID, 10) } + ps := h.popuniPodaciStranice(r, podesavanja) + ps.Stranica = "magacin" + ps.NaslovStranice = "Izmeni artikal" h.renderujFormuArtikla(w, PodaciFormeArtikla{ - PodaciStranice: model.PodaciStranice{ - Stranica: "magacin", - NaslovStranice: "Izmeni artikal", - Tema: podesavanja["tema"], - NazivFirme: podesavanja["naziv_firme"], - Podnazlov: podesavanja["podnazlov"], - LogoTip: podesavanja["logo_tip"], - LogoPutanja: podesavanja["logo_putanja"], - Korisnik: "Admin", - }, + PodaciStranice: ps, Artikal: artikal, Kategorije: kategorije, KategorijaIDStr: katIDStr, diff --git a/internal/handler/nabavka.go b/internal/handler/nabavka.go index 4f7d575..c76d263 100644 --- a/internal/handler/nabavka.go +++ b/internal/handler/nabavka.go @@ -25,11 +25,11 @@ type PodaciNabavki struct { // PodaciFormeNabavke su podaci za formu unosa nove nabavke type PodaciFormeNabavke struct { model.PodaciStranice - Artikli []model.ArtikalSaKategorijom - ArtikliJSON template.JS // JSON niz artikala za Alpine.js — bezbedan za umetanje u