diff --git a/internal/handler/dashboard.go b/internal/handler/dashboard.go index dbf46da..e1d789e 100644 --- a/internal/handler/dashboard.go +++ b/internal/handler/dashboard.go @@ -9,15 +9,89 @@ import ( "ntech/internal/model" ) -// Dashboard renderuje početnu stranicu +// Dashboard renderuje početnu stranicu sa pravim podacima iz baze func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { - // čitamo sva podešavanja iz baze - podesavanja, err := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + ctx := r.Context() + + podesavanja, err := sqlite.DohvatiSvaPodesavanja(ctx, h.DB) if err != nil { http.Error(w, "Greška pri učitavanju podešavanja", http.StatusInternalServerError) return } + var brojArtikala, aktivniServisi, prodajaOvogMeseca, kriticnaZaliha int + + if err := h.DB.QueryRowContext(ctx, + "SELECT COUNT(*) FROM artikli", + ).Scan(&brojArtikala); err != nil { + log.Printf("dashboard: broj artikala: %v", err) + } + + if err := h.DB.QueryRowContext(ctx, ` + SELECT COUNT(*) FROM servisni_nalozi + WHERE status NOT IN ('Završeno', 'Preuzeto')`, + ).Scan(&aktivniServisi); err != nil { + log.Printf("dashboard: aktivni servisi: %v", err) + } + + if err := h.DB.QueryRowContext(ctx, ` + SELECT COUNT(*) FROM prodajni_nalozi + WHERE strftime('%Y-%m', datum) = strftime('%Y-%m', 'now', 'localtime')`, + ).Scan(&prodajaOvogMeseca); err != nil { + log.Printf("dashboard: prodaja ovog meseca: %v", err) + } + + if err := h.DB.QueryRowContext(ctx, + "SELECT COUNT(*) FROM artikli WHERE kolicina <= kolicina_min", + ).Scan(&kriticnaZaliha); err != nil { + log.Printf("dashboard: kriticna zaliha: %v", err) + } + + // poslednjih 5 servisnih naloga + servisRedovi, err := h.DB.QueryContext(ctx, ` + SELECT uredjaj, status FROM servisni_nalozi + ORDER BY datum_prijema DESC LIMIT 5`) + if err != nil { + log.Printf("dashboard: poslednji servisi: %v", err) + } + + var poslednjiServisi []model.StavkaServisa + if servisRedovi != nil { + defer servisRedovi.Close() + for servisRedovi.Next() { + var s model.StavkaServisa + if err := servisRedovi.Scan(&s.Uredjaj, &s.Status); err == nil { + s.BojaTacke = bojaTackeServisa(s.Status) + poslednjiServisi = append(poslednjiServisi, s) + } + } + } + + // artikli sa kritičnom zalihom, sortirani po količini rastuće + zaliheRedovi, err := h.DB.QueryContext(ctx, ` + SELECT naziv, kolicina FROM artikli + WHERE kolicina <= kolicina_min + ORDER BY kolicina ASC LIMIT 5`) + if err != nil { + log.Printf("dashboard: kriticne zalihe: %v", err) + } + + var kriticneZalihe []model.StavkaZalihe + if zaliheRedovi != nil { + defer zaliheRedovi.Close() + for zaliheRedovi.Next() { + var z model.StavkaZalihe + if err := zaliheRedovi.Scan(&z.Naziv, &z.Kolicina); err == nil { + if z.Kolicina == 0 { + z.BojaTacke = "#dc2626" + } else { + z.BojaTacke = "#f97316" + } + kriticneZalihe = append(kriticneZalihe, z) + } + } + } + podaci := model.PodaciDashboarda{ PodaciStranice: model.PodaciStranice{ Stranica: "dashboard", @@ -29,12 +103,12 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { LogoPutanja: podesavanja["logo_putanja"], Korisnik: "Admin", }, - BrojArtikala: 0, - AktivniServisi: 0, - ProdajaOvogMeseca: 0, - KriticnaZaliha: 0, - PoslednjiServisi: []model.StavkaServisa{}, - KriticneZalihe: []model.StavkaZalihe{}, + BrojArtikala: brojArtikala, + AktivniServisi: aktivniServisi, + ProdajaOvogMeseca: prodajaOvogMeseca, + KriticnaZaliha: kriticnaZaliha, + PoslednjiServisi: poslednjiServisi, + KriticneZalihe: kriticneZalihe, } tmpl, err := template.ParseFiles( @@ -44,6 +118,7 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { "web/templates/stranice/dashboard.html", ) if err != nil { + log.Printf("greška pri učitavanju šablona: %v", err) http.Error(w, "Greška pri učitavanju stranice", http.StatusInternalServerError) return } @@ -51,6 +126,23 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { if err := tmpl.ExecuteTemplate(w, "base", podaci); err != nil { log.Printf("greška pri renderovanju: %v", err) http.Error(w, "Greška pri prikazu stranice", http.StatusInternalServerError) - return + } +} + +// bojaTackeServisa vraća hex boju tačke prema statusu naloga +func bojaTackeServisa(status string) string { + switch status { + case "U dijagnostici": + return "#3b82f6" + case "Čeka delove": + return "#f97316" + case "U popravci": + return "#ca8a04" + case "Završeno": + return "#16a34a" + case "Preuzeto": + return "#15803d" + default: + return "#94a3b8" } } diff --git a/internal/handler/prodaja.go b/internal/handler/prodaja.go index 671f52d..d33cff2 100644 --- a/internal/handler/prodaja.go +++ b/internal/handler/prodaja.go @@ -44,16 +44,17 @@ type PodaciDetaljiProdaje struct { Sacuvano bool } -// artikalUJSONSaCenom pretvara listu artikala u template.JS vrednost sa prodajnom cenom +// artikalUJSONSaCenom pretvara listu artikala u template.JS vrednost sa prodajnom cenom i stanjem func artikalUJSONSaCenom(artikli []model.ArtikalSaKategorijom) template.JS { type stavka struct { - ID int64 `json:"id"` - Naziv string `json:"naziv"` - Cena float64 `json:"cena"` + ID int64 `json:"id"` + Naziv string `json:"naziv"` + Cena float64 `json:"cena"` + Kolicina int `json:"kolicina"` } lista := make([]stavka, 0, len(artikli)) for _, a := range artikli { - lista = append(lista, stavka{ID: a.ID, Naziv: a.Naziv, Cena: a.ProdajnaCena}) + lista = append(lista, stavka{ID: a.ID, Naziv: a.Naziv, Cena: a.ProdajnaCena, Kolicina: a.Kolicina}) } b, _ := json.Marshal(lista) return template.JS(b) diff --git a/web/templates/stranice/nabavka_forma.html b/web/templates/stranice/nabavka_forma.html index bcc4c3d..bbdd74a 100644 --- a/web/templates/stranice/nabavka_forma.html +++ b/web/templates/stranice/nabavka_forma.html @@ -49,9 +49,14 @@