Dashboard — pravi podaci, prihod meseca, poslednje prodaje, datum umesto badge

This commit is contained in:
2026-06-02 18:29:59 +02:00
parent 5c744ed15e
commit a6c4f429e4
3 changed files with 90 additions and 28 deletions
+43 -13
View File
@@ -4,6 +4,7 @@ import (
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"time"
"ntech/internal/db/sqlite" "ntech/internal/db/sqlite"
"ntech/internal/model" "ntech/internal/model"
@@ -19,7 +20,8 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
return return
} }
var brojArtikala, aktivniServisi, prodajaOvogMeseca, kriticnaZaliha int var brojArtikala, aktivniServisi, kriticnaZaliha int
var prihodOvogMeseca float64
if err := h.DB.QueryRowContext(ctx, if err := h.DB.QueryRowContext(ctx,
"SELECT COUNT(*) FROM artikli", "SELECT COUNT(*) FROM artikli",
@@ -35,10 +37,10 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
} }
if err := h.DB.QueryRowContext(ctx, ` if err := h.DB.QueryRowContext(ctx, `
SELECT COUNT(*) FROM prodajni_nalozi SELECT COALESCE(SUM(ukupno), 0) FROM prodajni_nalozi
WHERE strftime('%Y-%m', datum) = strftime('%Y-%m', 'now', 'localtime')`, WHERE strftime('%Y-%m', datum) = strftime('%Y-%m', 'now', 'localtime')`,
).Scan(&prodajaOvogMeseca); err != nil { ).Scan(&prihodOvogMeseca); err != nil {
log.Printf("dashboard: prodaja ovog meseca: %v", err) log.Printf("dashboard: prihod ovog meseca: %v", err)
} }
if err := h.DB.QueryRowContext(ctx, if err := h.DB.QueryRowContext(ctx,
@@ -47,9 +49,9 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
log.Printf("dashboard: kriticna zaliha: %v", err) log.Printf("dashboard: kriticna zaliha: %v", err)
} }
// poslednjih 5 servisnih naloga // poslednjih 5 servisnih naloga sa datumom prijema
servisRedovi, err := h.DB.QueryContext(ctx, ` servisRedovi, err := h.DB.QueryContext(ctx, `
SELECT uredjaj, status FROM servisni_nalozi SELECT uredjaj, status, datum_prijema FROM servisni_nalozi
ORDER BY datum_prijema DESC LIMIT 5`) ORDER BY datum_prijema DESC LIMIT 5`)
if err != nil { if err != nil {
log.Printf("dashboard: poslednji servisi: %v", err) log.Printf("dashboard: poslednji servisi: %v", err)
@@ -60,8 +62,10 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
defer servisRedovi.Close() defer servisRedovi.Close()
for servisRedovi.Next() { for servisRedovi.Next() {
var s model.StavkaServisa var s model.StavkaServisa
if err := servisRedovi.Scan(&s.Uredjaj, &s.Status); err == nil { var datum time.Time
if err := servisRedovi.Scan(&s.Uredjaj, &s.Status, &datum); err == nil {
s.BojaTacke = bojaTackeServisa(s.Status) s.BojaTacke = bojaTackeServisa(s.Status)
s.DatumPrijema = datum.Format("02.01.")
poslednjiServisi = append(poslednjiServisi, s) poslednjiServisi = append(poslednjiServisi, s)
} }
} }
@@ -92,6 +96,31 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
} }
} }
// poslednjih 5 prodajnih naloga sa nazivom klijenta
prodajaRedovi, err := h.DB.QueryContext(ctx, `
SELECT
pn.broj_naloga, pn.ukupno, pn.datum,
COALESCE(NULLIF(k.naziv_firme, ''), TRIM(COALESCE(k.ime, '') || ' ' || COALESCE(k.prezime, '')), '') AS klijent_naziv
FROM prodajni_nalozi pn
LEFT JOIN klijenti k ON k.id = pn.klijent_id
ORDER BY pn.datum DESC LIMIT 5`)
if err != nil {
log.Printf("dashboard: poslednje prodaje: %v", err)
}
var poslednjeProdaje []model.StavkaProdajePregled
if prodajaRedovi != nil {
defer prodajaRedovi.Close()
for prodajaRedovi.Next() {
var p model.StavkaProdajePregled
var datum time.Time
if err := prodajaRedovi.Scan(&p.BrojNaloga, &p.Ukupno, &datum, &p.KlijentNaziv); err == nil {
p.Datum = datum.Format("02.01.")
poslednjeProdaje = append(poslednjeProdaje, p)
}
}
}
podaci := model.PodaciDashboarda{ podaci := model.PodaciDashboarda{
PodaciStranice: model.PodaciStranice{ PodaciStranice: model.PodaciStranice{
Stranica: "dashboard", Stranica: "dashboard",
@@ -103,12 +132,13 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
LogoPutanja: podesavanja["logo_putanja"], LogoPutanja: podesavanja["logo_putanja"],
Korisnik: "Admin", Korisnik: "Admin",
}, },
BrojArtikala: brojArtikala, BrojArtikala: brojArtikala,
AktivniServisi: aktivniServisi, AktivniServisi: aktivniServisi,
ProdajaOvogMeseca: prodajaOvogMeseca, PrihodOvogMeseca: prihodOvogMeseca,
KriticnaZaliha: kriticnaZaliha, KriticnaZaliha: kriticnaZaliha,
PoslednjiServisi: poslednjiServisi, PoslednjiServisi: poslednjiServisi,
KriticneZalihe: kriticneZalihe, KriticneZalihe: kriticneZalihe,
PoslednjeProdaje: poslednjeProdaje,
} }
tmpl, err := template.ParseFiles( tmpl, err := template.ParseFiles(
+19 -9
View File
@@ -2,9 +2,10 @@ package model
// StavkaServisa prikazuje jedan servisni nalog na dashboardu // StavkaServisa prikazuje jedan servisni nalog na dashboardu
type StavkaServisa struct { type StavkaServisa struct {
Uredjaj string Uredjaj string
Status string Status string
BojaTacke string BojaTacke string
DatumPrijema string // kratki format, npr. "01.06."
} }
// StavkaZalihe prikazuje jedan artikal sa kritičnom zalihom // StavkaZalihe prikazuje jedan artikal sa kritičnom zalihom
@@ -14,6 +15,14 @@ type StavkaZalihe struct {
BojaTacke string BojaTacke string
} }
// StavkaProdajePregled prikazuje jedan prodajni nalog na dashboardu
type StavkaProdajePregled struct {
BrojNaloga string
KlijentNaziv string
Ukupno float64
Datum string // kratki format, npr. "01.06."
}
// PodaciStranice su zajednički podaci koje svaka stranica prima // PodaciStranice su zajednički podaci koje svaka stranica prima
type PodaciStranice struct { type PodaciStranice struct {
Stranica string Stranica string
@@ -29,10 +38,11 @@ type PodaciStranice struct {
// PodaciDashboarda su podaci specifični za dashboard stranicu // PodaciDashboarda su podaci specifični za dashboard stranicu
type PodaciDashboarda struct { type PodaciDashboarda struct {
PodaciStranice PodaciStranice
BrojArtikala int BrojArtikala int
AktivniServisi int AktivniServisi int
ProdajaOvogMeseca int PrihodOvogMeseca float64
KriticnaZaliha int KriticnaZaliha int
PoslednjiServisi []StavkaServisa PoslednjiServisi []StavkaServisa
KriticneZalihe []StavkaZalihe KriticneZalihe []StavkaZalihe
PoslednjeProdaje []StavkaProdajePregled
} }
+28 -6
View File
@@ -25,8 +25,8 @@
<div style="width:36px;height:36px;border-radius:8px;background:#fff7ed;display:flex;align-items:center;justify-content:center;margin-bottom:10px;"> <div style="width:36px;height:36px;border-radius:8px;background:#fff7ed;display:flex;align-items:center;justify-content:center;margin-bottom:10px;">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ea580c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ea580c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
</div> </div>
<div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{.ProdajaOvogMeseca}}</div> <div style="font-size:22px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .PrihodOvogMeseca}} din</div>
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Prodaja ovog meseca</div> <div style="font-size:12px;color:var(--tekst-sporedni);margin-top:4px;">Prihod ovog meseca</div>
</div> </div>
<div class="kartica"> <div class="kartica">
@@ -38,18 +38,17 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- poslednji servisi --> <!-- poslednji servisi -->
<div class="kartica"> <div class="kartica">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;"> <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
<span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Poslednji servisi</span> <span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Poslednji servisi</span>
<span style="font-size:11px;padding:2px 8px;border-radius:20px;background:#eff2ff;color:#4f7ef8;font-weight:500;">Danas</span>
</div> </div>
{{range .PoslednjiServisi}} {{range .PoslednjiServisi}}
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);"> <div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{.BojaTacke}};"></div> <div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{.BojaTacke}};"></div>
<span style="font-size:13px;color:var(--tekst-glavni);flex:1;">{{.Uredjaj}}</span> <span style="font-size:13px;color:var(--tekst-glavni);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{.Uredjaj}}</span>
<span style="font-size:12px;color:var(--tekst-sporedni);">{{.Status}}</span> <span style="font-size:11px;color:var(--tekst-sporedni);white-space:nowrap;">{{.DatumPrijema}}</span>
</div> </div>
{{else}} {{else}}
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Nema servisnih naloga.</div> <div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Nema servisnih naloga.</div>
@@ -72,5 +71,28 @@
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Sve zalihe su uredne.</div> <div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Sve zalihe su uredne.</div>
{{end}} {{end}}
</div> </div>
<!-- poslednje prodaje -->
<div class="kartica">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
<span style="font-size:14px;font-weight:500;color:var(--tekst-glavni);">Poslednje prodaje</span>
</div>
{{range .PoslednjeProdaje}}
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:0.5px solid var(--ivica);">
<div style="flex:1;min-width:0;">
<div style="font-size:13px;color:var(--tekst-glavni);font-family:monospace;">{{.BrojNaloga}}</div>
{{if .KlijentNaziv}}
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{{.KlijentNaziv}}</div>
{{end}}
</div>
<div style="text-align:right;flex-shrink:0;">
<div style="font-size:13px;font-weight:500;color:var(--tekst-glavni);">{{printf "%.0f" .Ukupno}} din</div>
<div style="font-size:11px;color:var(--tekst-sporedni);margin-top:1px;">{{.Datum}}</div>
</div>
</div>
{{else}}
<div style="font-size:13px;color:var(--tekst-sporedni);padding:8px 0;">Nema prodajnih naloga.</div>
{{end}}
</div>
</div> </div>
{{end}} {{end}}