From 2937acfcc1be9e05f23d1474ccfd6c5b93663679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dalibor=20Markovi=C4=87?= Date: Sat, 20 Jun 2026 13:04:23 +0200 Subject: [PATCH] =?UTF-8?q?Servis:=20javni=20status=20nalog=20+=20ispravke?= =?UTF-8?q?=20AJAX=20=C4=8Duvanja?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dodat javni token na servisni nalog (migracija 057), QR kod vodi na /status/{token} - Nova javna stranica /status/{token} — bez prijave, za klijente - Sve forme sa "Sačuvaj izmene" koriste ?sacuvano=1 umesto SetFlash za uspeh - AJAX logika: toast + ostanak samo kad pathname ostaje isti; inače navigacija - Ispravke: PDV stope, KIR, KPR, podešavanja izgled, storno prodaje, nivelacija, delovi naloga --- cmd/ntech/main.go | 1 + internal/db/repository.go | 1 + internal/db/sqlite/servis.go | 50 ++++- internal/handler/kes.go | 26 ++- internal/handler/nivelacija.go | 5 +- internal/handler/pdv_kir.go | 6 +- internal/handler/pdv_kpr.go | 6 +- internal/handler/pdv_stopa.go | 13 +- internal/handler/podesavanja.go | 6 +- internal/handler/prodaja.go | 3 +- internal/handler/servis.go | 46 +++- internal/model/servis.go | 1 + migrations/057_servis_javni_token.sql | 2 + .../stranice/servis_status_javni.html | 208 ++++++++++++++++++ web/templates/teme/podrazumevana/base.html | 8 +- 15 files changed, 335 insertions(+), 47 deletions(-) create mode 100644 migrations/057_servis_javni_token.sql create mode 100644 web/templates/stranice/servis_status_javni.html diff --git a/cmd/ntech/main.go b/cmd/ntech/main.go index cf39326..079452b 100644 --- a/cmd/ntech/main.go +++ b/cmd/ntech/main.go @@ -239,6 +239,7 @@ func main() { r.Get("/setup", h.PrikazSetupa) r.Post("/setup", h.SacuvajSetup) r.Get("/odjava", h.Odjava) + r.Get("/status/{token}", h.ServisJavniStatus) // zaštićene rute — zahtevaju prijavljenog korisnika r.Group(func(r chi.Router) { diff --git a/internal/db/repository.go b/internal/db/repository.go index 71b7f07..c5c0fcf 100644 --- a/internal/db/repository.go +++ b/internal/db/repository.go @@ -112,6 +112,7 @@ type KlijentRepository interface { type ServisRepository interface { Lista(ctx context.Context, pretraga, status string) ([]model.ServisniNalogSaKlijentom, error) DohvatiID(ctx context.Context, id int64) (*model.ServisniNalog, error) + DohvatiJavniToken(ctx context.Context, token string) (*model.ServisniNalog, error) Kreiraj(ctx context.Context, n *model.ServisniNalog) (int64, error) Izmeni(ctx context.Context, n *model.ServisniNalog) error AzurirajStatus(ctx context.Context, id int64, status string) error diff --git a/internal/db/sqlite/servis.go b/internal/db/sqlite/servis.go index 8d5d51c..072637b 100644 --- a/internal/db/sqlite/servis.go +++ b/internal/db/sqlite/servis.go @@ -2,13 +2,24 @@ package sqlite import ( "context" + "crypto/rand" "database/sql" + "encoding/hex" "fmt" "time" "ntech/internal/model" ) +// generisiJavniToken kreira 32-znakovni hex token za javni URL +func generisiJavniToken() (string, error) { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + // ServisRepo je SQLite implementacija ServisRepository interfejsa type ServisRepo struct { db *sql.DB @@ -43,7 +54,7 @@ func (r *ServisRepo) Lista(ctx context.Context, pretraga, status string) ([]mode sn.id, sn.klijent_id, sn.tehnicar_id, sn.broj_naloga, sn.uredjaj, sn.serijski_broj, sn.opis_kvara, sn.status, sn.cena_od, sn.cena_do, sn.cena_konacna, sn.avans, sn.napomena, sn.garancija_do, sn.datum_prijema, sn.datum_zavrsetka, - sn.ostecenja, sn.pin_uredjaja, sn.pribor, + sn.ostecenja, sn.pin_uredjaja, sn.pribor, sn.javni_token, COALESCE(kp.naziv, '') AS klijent_naziv FROM servisni_nalozi sn LEFT JOIN klijent_prikaz kp ON kp.id = sn.klijent_id @@ -90,7 +101,7 @@ func (r *ServisRepo) DohvatiID(ctx context.Context, id int64) (*model.ServisniNa id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, opis_kvara, status, cena_od, cena_do, cena_konacna, avans, napomena, garancija_do, datum_prijema, datum_zavrsetka, - ostecenja, pin_uredjaja, pribor + ostecenja, pin_uredjaja, pribor, javni_token FROM servisni_nalozi WHERE id = ?`, id) var n model.ServisniNalog @@ -102,21 +113,26 @@ func (r *ServisRepo) DohvatiID(ctx context.Context, id int64) (*model.ServisniNa return &n, nil } -// Kreiraj upisuje novi servisni nalog u bazu +// Kreiraj upisuje novi servisni nalog u bazu i generiše javni token func (r *ServisRepo) Kreiraj(ctx context.Context, n *model.ServisniNalog) (int64, error) { + token, err := generisiJavniToken() + if err != nil { + return 0, fmt.Errorf("ntech: ServisRepo.Kreiraj: token: %w", err) + } + rezultat, err := r.db.ExecContext(ctx, ` INSERT INTO servisni_nalozi (klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, opis_kvara, status, cena_od, cena_do, cena_konacna, avans, napomena, garancija_do, datum_zavrsetka, - ostecenja, pin_uredjaja, pribor, datum_prijema) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ostecenja, pin_uredjaja, pribor, datum_prijema, javni_token) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, nullInt64(n.KlijentID), nullInt64(n.TehnicarID), n.BrojNaloga, n.Uredjaj, nullString(n.SerijskiBroj), n.OpisKvara, n.Status, nullFloat64(n.CenaOd), nullFloat64(n.CenaDo), nullFloat64(n.CenaKonacna), nullFloat64(n.Avans), nullString(n.Napomena), nullTime(n.GarancijaDo), nullTime(n.DatumZavrsetka), nullString(n.Ostecenja), nullString(n.PinUredjaja), nullString(n.Pribor), - n.DatumPrijema, + n.DatumPrijema, token, ) if err != nil { return 0, fmt.Errorf("ntech: ServisRepo.Kreiraj: %w", err) @@ -130,6 +146,23 @@ func (r *ServisRepo) Kreiraj(ctx context.Context, n *model.ServisniNalog) (int64 return id, nil } +// DohvatiJavniToken vraća servisni nalog po javnom tokenu — bez autentifikacije +func (r *ServisRepo) DohvatiJavniToken(ctx context.Context, token string) (*model.ServisniNalog, error) { + red := r.db.QueryRowContext(ctx, ` + SELECT + id, klijent_id, tehnicar_id, broj_naloga, uredjaj, serijski_broj, + opis_kvara, status, cena_od, cena_do, cena_konacna, + avans, napomena, garancija_do, datum_prijema, datum_zavrsetka, + ostecenja, pin_uredjaja, pribor, javni_token + FROM servisni_nalozi WHERE javni_token = ?`, token) + + var n model.ServisniNalog + if err := scanNalog(red.Scan, &n, nil); err != nil { + return nil, fmt.Errorf("ntech: ServisRepo.DohvatiJavniToken: %w", err) + } + return &n, nil +} + // Izmeni ažurira postojeći servisni nalog — broj_naloga i datum_prijema se ne menjaju func (r *ServisRepo) Izmeni(ctx context.Context, n *model.ServisniNalog) error { _, err := r.db.ExecContext(ctx, ` @@ -184,7 +217,7 @@ func (r *ServisRepo) Obrisi(ctx context.Context, id int64) error { // klijentNaziv je opcioni pokazivač, nil kada se čita bez JOIN-a func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *string) error { var klijentID, tehnicarID sql.NullInt64 - var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor sql.NullString + var serijskiBroj, napomena, ostecenja, pinUredjaja, pribor, javniToken sql.NullString var cenaOd, cenaDo, cenaKonacna, avans sql.NullFloat64 var garancijaDo, datumZavrsetka sql.NullTime @@ -192,7 +225,7 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st &n.ID, &klijentID, &tehnicarID, &n.BrojNaloga, &n.Uredjaj, &serijskiBroj, &n.OpisKvara, &n.Status, &cenaOd, &cenaDo, &cenaKonacna, &avans, &napomena, &garancijaDo, &n.DatumPrijema, &datumZavrsetka, - &ostecenja, &pinUredjaja, &pribor, + &ostecenja, &pinUredjaja, &pribor, &javniToken, } if klijentNaziv != nil { @@ -240,6 +273,7 @@ func scanNalog(scan func(...any) error, n *model.ServisniNalog, klijentNaziv *st v := datumZavrsetka.Time n.DatumZavrsetka = &v } + n.JavniToken = javniToken.String return nil } diff --git a/internal/handler/kes.go b/internal/handler/kes.go index 8a05553..29ad212 100644 --- a/internal/handler/kes.go +++ b/internal/handler/kes.go @@ -5,6 +5,7 @@ import ( "html/template" "io/fs" "log/slog" + "math" "net/http" ) @@ -38,13 +39,33 @@ var saSidebar = []string{ // standalone su šabloni bez base layouta var standaloneIme = []string{ - "prijava", "setup", "totp_provera", "prodaja_stampa", "servis_stampa", "servis_otpremnica", + "prijava", "setup", "totp_provera", "prodaja_stampa", "servis_stampa", "servis_otpremnica", "servis_status_javni", } // sablonskeFunkcije su pomoćne funkcije dostupne u svim šablonima. // dict gradi mapu iz parova ključ/vrednost — koristi se da se jednom partialu // prosledi više vrednosti (npr. {{template "x" (dict "ID" .ID "Lista" $.Lista)}}). var sablonskeFunkcije = template.FuncMap{ + // formatBroj formatira float pointer kao ceo broj (zaokružen) — nil vraca "" + "formatBroj": func(v *float64) string { + if v == nil { + return "" + } + return fmt.Sprintf("%d", int64(math.Round(*v))) + }, + // statusPre vraća true ako je `a` pre `b` u redosledu statusa + "statusPre": func(a, b string, statusi []string) bool { + ia, ib := -1, -1 + for i, s := range statusi { + if s == a { + ia = i + } + if s == b { + ib = i + } + } + return ia >= 0 && ib >= 0 && ia < ib + }, "dict": func(parovi ...any) (map[string]any, error) { if len(parovi)%2 != 0 { return nil, fmt.Errorf("dict: neparan broj argumenata") @@ -77,7 +98,8 @@ func KreirajKes(fsys fs.FS) (map[string]*template.Template, error) { } for _, ime := range standaloneIme { - t, err := template.ParseFS(fsys, "web/templates/stranice/"+ime+".html") + // ime+".html" mora biti ime roota da bi Execute() pronašlo sadržaj fajla + t, err := template.New(ime+".html").Funcs(sablonskeFunkcije).ParseFS(fsys, "web/templates/stranice/"+ime+".html") if err != nil { return nil, fmt.Errorf("kes: %s: %w", ime, err) } diff --git a/internal/handler/nivelacija.go b/internal/handler/nivelacija.go index 2b35347..cd548b7 100644 --- a/internal/handler/nivelacija.go +++ b/internal/handler/nivelacija.go @@ -93,10 +93,11 @@ func (h *Handler) PromeniCenuArtikla(w http.ResponseWriter, r *http.Request) { switch { case errors.Is(err, sqlite.ErrArtikalNePostoji): middleware.SetFlash(w, r, h.DB, "greska", "Artikal nije pronađen.") + http.Redirect(w, r, "/magacin", http.StatusSeeOther) case err != nil: middleware.SetFlash(w, r, h.DB, "greska", "Greška pri promeni cene.") + http.Redirect(w, r, "/magacin", http.StatusSeeOther) default: - middleware.SetFlash(w, r, h.DB, "uspeh", "Prodajna cena je izmenjena.") + http.Redirect(w, r, "/magacin?sacuvano=1", http.StatusSeeOther) } - http.Redirect(w, r, "/magacin", http.StatusSeeOther) } diff --git a/internal/handler/pdv_kir.go b/internal/handler/pdv_kir.go index 73dd50b..6d63954 100644 --- a/internal/handler/pdv_kir.go +++ b/internal/handler/pdv_kir.go @@ -169,8 +169,7 @@ func (h *Handler) SacuvajPdvKir(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri čuvanju zapisa", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Izlazni račun je dodat u KIR.") - http.Redirect(w, r, "/pdv/kir", http.StatusSeeOther) + http.Redirect(w, r, "/pdv/kir?sacuvano=1", http.StatusSeeOther) } // ObrisiPdvKir briše zapis iz KIR @@ -187,6 +186,5 @@ func (h *Handler) ObrisiPdvKir(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri brisanju zapisa", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Zapis je obrisan iz KIR.") - http.Redirect(w, r, "/pdv/kir", http.StatusSeeOther) + http.Redirect(w, r, "/pdv/kir?obrisan=1", http.StatusSeeOther) } diff --git a/internal/handler/pdv_kpr.go b/internal/handler/pdv_kpr.go index 956fb13..37c0b74 100644 --- a/internal/handler/pdv_kpr.go +++ b/internal/handler/pdv_kpr.go @@ -154,8 +154,7 @@ func (h *Handler) SacuvajPdvKpr(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri čuvanju zapisa", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Ulazni račun je dodat u KPR.") - http.Redirect(w, r, "/pdv/kpr", http.StatusSeeOther) + http.Redirect(w, r, "/pdv/kpr?sacuvano=1", http.StatusSeeOther) } // ObrisiPdvKpr briše zapis iz KPR @@ -172,6 +171,5 @@ func (h *Handler) ObrisiPdvKpr(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri brisanju zapisa", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Zapis je obrisan iz KPR.") - http.Redirect(w, r, "/pdv/kpr", http.StatusSeeOther) + http.Redirect(w, r, "/pdv/kpr?obrisan=1", http.StatusSeeOther) } diff --git a/internal/handler/pdv_stopa.go b/internal/handler/pdv_stopa.go index d0a85f6..65ecaea 100644 --- a/internal/handler/pdv_stopa.go +++ b/internal/handler/pdv_stopa.go @@ -99,8 +99,7 @@ func (h *Handler) DodajPdvStopu(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri čuvanju PDV stope", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je dodata.") - http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther) + http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv?sacuvano=1", http.StatusSeeOther) } // IzmeniPdvStopu prima POST i menja postojeću stopu @@ -128,8 +127,7 @@ func (h *Handler) IzmeniPdvStopu(w http.ResponseWriter, r *http.Request) { http.Error(w, "Greška pri izmeni PDV stope", http.StatusInternalServerError) return } - middleware.SetFlash(w, r, h.DB, "uspeh", "PDV stopa je izmenjena.") - http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther) + http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv?sacuvano=1", http.StatusSeeOther) } // PromeniAktivnostPdvStope arhivira ili vraća stopu u upotrebu (toggle, bez brisanja) @@ -151,10 +149,5 @@ func (h *Handler) PromeniAktivnostPdvStope(w http.ResponseWriter, r *http.Reques http.Error(w, "Greška pri promeni statusa PDV stope", http.StatusInternalServerError) return } - poruka := "PDV stopa je arhivirana." - if !postojeca.Aktivna { - poruka = "PDV stopa je vraćena u upotrebu." - } - middleware.SetFlash(w, r, h.DB, "uspeh", poruka) - http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv", http.StatusSeeOther) + http.Redirect(w, r, "/admin/podesavanja/kalkulacija-pdv?sacuvano=1", http.StatusSeeOther) } diff --git a/internal/handler/podesavanja.go b/internal/handler/podesavanja.go index e3be568..678f623 100644 --- a/internal/handler/podesavanja.go +++ b/internal/handler/podesavanja.go @@ -600,8 +600,7 @@ func (h *Handler) UkloniLoginPozadinu(w http.ResponseWriter, r *http.Request) { return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Pozadinska slika je uklonjena.") - http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther) + http.Redirect(w, r, "/admin/podesavanja/izgled?sacuvano=1", http.StatusSeeOther) } // SacuvajLoginPozadinaStilove čuva vrednosti zamućenja i prozirnosti pozadine login stranice @@ -659,8 +658,7 @@ func (h *Handler) SacuvajLoginPozadinaStilove(w http.ResponseWriter, r *http.Req } } - middleware.SetFlash(w, r, h.DB, "uspeh", "Izgled pozadine prijave je sačuvan.") - http.Redirect(w, r, "/admin/podesavanja/izgled", http.StatusSeeOther) + http.Redirect(w, r, "/admin/podesavanja/izgled?sacuvano=1", http.StatusSeeOther) } // napuniPodaciPodesavanja učitava sva podešavanja i kreira strukturu za template diff --git a/internal/handler/prodaja.go b/internal/handler/prodaja.go index 0f3ebda..9311ffd 100644 --- a/internal/handler/prodaja.go +++ b/internal/handler/prodaja.go @@ -432,8 +432,7 @@ func (h *Handler) StornoProdaje(w http.ResponseWriter, r *http.Request) { if err := h.PdvKirRepo.ObrisiPoIzvoru(r.Context(), "prodaja", id); err != nil { slog.Error("brisanje vezanog KIR zapisa nije uspelo", "prodaja_id", id, "error", err) } - middleware.SetFlash(w, r, h.DB, "uspeh", "Prodajni nalog je storniran.") - http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10), http.StatusSeeOther) + http.Redirect(w, r, "/prodaja/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther) } // renderujFormuProdaje renderuje HTML šablon forme za unos nove prodaje diff --git a/internal/handler/servis.go b/internal/handler/servis.go index fc7c5fd..6b62ae2 100644 --- a/internal/handler/servis.go +++ b/internal/handler/servis.go @@ -444,8 +444,7 @@ func (h *Handler) DodajDeloNalogu(w http.ResponseWriter, r *http.Request) { return } - middleware.SetFlash(w, r, h.DB, "uspeh", "Deo je dodat.") - http.Redirect(w, r, "/servis/"+strconv.FormatInt(nalogID, 10), http.StatusSeeOther) + http.Redirect(w, r, "/servis/"+strconv.FormatInt(nalogID, 10)+"?sacuvano=1", http.StatusSeeOther) } // ObrisiDeloNaloga prima POST zahtev i uklanja deo iz servisnog naloga @@ -470,10 +469,7 @@ func (h *Handler) ObrisiDeloNaloga(w http.ResponseWriter, r *http.Request) { if err := h.ServisniDeloviRepo.Obrisi(r.Context(), deoID, &k.ID); err != nil { slog.Error("greška pri brisanju dela", "error", err) middleware.SetFlash(w, r, h.DB, "greska", "Greška pri uklanjanju dela.") - } else { - middleware.SetFlash(w, r, h.DB, "uspeh", "Deo je uklonjen.") } - http.Redirect(w, r, "/servis/"+strconv.FormatInt(nalogID, 10), http.StatusSeeOther) } @@ -647,12 +643,12 @@ func (h *Handler) StampaServisa(w http.ResponseWriter, r *http.Request) { ukupnoDelovi += d.Ukupno() } - // QR kod sadrži URL naloga — isti host kao što korisnik koristi + // QR kod vodi na javnu status stranicu — dostupnu bez prijave scheme := "http" if r.TLS != nil { scheme = "https" } - nalogURL := scheme + "://" + r.Host + "/servis/" + strconv.FormatInt(id, 10) + nalogURL := scheme + "://" + r.Host + "/status/" + nalog.JavniToken var qrKod string if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil { qrKod = base64.StdEncoding.EncodeToString(png) @@ -762,7 +758,7 @@ func (h *Handler) StampaOtpremnice(w http.ResponseWriter, r *http.Request) { if r.TLS != nil { nalogURL += "s" } - nalogURL += "://" + r.Host + "/servis/" + strconv.FormatInt(id, 10) + nalogURL += "://" + r.Host + "/status/" + nalog.JavniToken var qrKodOtpr string if png, err := qrcode.Encode(nalogURL, qrcode.Medium, 160); err == nil { qrKodOtpr = base64.StdEncoding.EncodeToString(png) @@ -813,3 +809,37 @@ func (h *Handler) PromeniStatus(w http.ResponseWriter, r *http.Request) { } http.Redirect(w, r, "/servis/"+strconv.FormatInt(id, 10)+"?sacuvano=1", http.StatusSeeOther) } + +// PodaciJavnogStatusa su podaci za javnu status stranicu servisnog naloga +type PodaciJavnogStatusa struct { + Nalog model.ServisniNalog + NazivFirme string + Telefon string + Adresa string + SviStatusi []string +} + +// ServisJavniStatus prikazuje javnu status stranicu — dostupna bez prijave putem QR koda +func (h *Handler) ServisJavniStatus(w http.ResponseWriter, r *http.Request) { + token := chi.URLParam(r, "token") + if token == "" { + http.NotFound(w, r) + return + } + + nalog, err := h.ServisRepo.DohvatiJavniToken(r.Context(), token) + if err != nil { + http.NotFound(w, r) + return + } + + podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB) + + h.renderujStandalone(w, "servis_status_javni", PodaciJavnogStatusa{ + Nalog: *nalog, + NazivFirme: podesavanja["naziv_firme"], + Telefon: podesavanja["telefon"], + Adresa: podesavanja["adresa"], + SviStatusi: model.SviStatusi, + }) +} diff --git a/internal/model/servis.go b/internal/model/servis.go index d34723e..4ea0899 100644 --- a/internal/model/servis.go +++ b/internal/model/servis.go @@ -46,6 +46,7 @@ type ServisniNalog struct { Ostecenja string PinUredjaja string Pribor string + JavniToken string } // ServisniDeo predstavlja jedan artikal ugrađen u servisni nalog diff --git a/migrations/057_servis_javni_token.sql b/migrations/057_servis_javni_token.sql new file mode 100644 index 0000000..f51f508 --- /dev/null +++ b/migrations/057_servis_javni_token.sql @@ -0,0 +1,2 @@ +ALTER TABLE servisni_nalozi ADD COLUMN javni_token TEXT; +CREATE UNIQUE INDEX IF NOT EXISTS idx_servisni_nalozi_javni_token ON servisni_nalozi(javni_token); diff --git a/web/templates/stranice/servis_status_javni.html b/web/templates/stranice/servis_status_javni.html new file mode 100644 index 0000000..c39f643 --- /dev/null +++ b/web/templates/stranice/servis_status_javni.html @@ -0,0 +1,208 @@ + + + + + + Status popravke — {{.Nalog.BrojNaloga}} + + + +
+ + +
+
{{if .NazivFirme}}{{.NazivFirme}}{{else}}Servis{{end}}
+ {{if .Adresa}}
{{.Adresa}}
{{end}} +
+ + +
+
Vaš uređaj
+
{{.Nalog.Uredjaj}}
+
Nalog: {{.Nalog.BrojNaloga}}
+ + {{$s := .Nalog.Status}} + {{if eq $s "Primljeno"}} + Primljeno + {{else if eq $s "U dijagnostici"}} + U dijagnostici + {{else if eq $s "Čeka delove"}} + Čeka delove + {{else if eq $s "U popravci"}} + U popravci + {{else if eq $s "Završeno"}} + Završeno — možete preuzeti + {{else if eq $s "Preuzeto"}} + Preuzeto + {{end}} + + +
+
Napredak popravke
+
+ {{range $i, $korak := .SviStatusi}} + {{if eq $korak $s}} +
+ {{else if statusPre $korak $s $.SviStatusi}} +
+ {{else}} +
+ {{end}} + {{end}} +
+
+
+ + +
+
Detalji
+ +
+ Datum prijema + {{.Nalog.DatumPrijema.Format "02.01.2006."}} +
+ + {{if .Nalog.DatumZavrsetka}} +
+ Datum završetka + {{.Nalog.DatumZavrsetka.Format "02.01.2006."}} +
+ {{end}} + + {{if .Nalog.SerijskiBroj}} +
+ Serijski broj + {{.Nalog.SerijskiBroj}} +
+ {{end}} + + {{if .Nalog.OpisKvara}} +
+ Opis kvara + {{.Nalog.OpisKvara}} +
+ {{end}} + + {{if .Nalog.GarancijaDo}} +
+ Garancija do + {{.Nalog.GarancijaDo.Format "02.01.2006."}} +
+ {{end}} + + + {{if or .Nalog.CenaOd .Nalog.CenaDo .Nalog.CenaKonacna}} +
+
Procena cene
+
+ {{if .Nalog.CenaKonacna}} +
+ {{formatBroj .Nalog.CenaKonacna}} din +
+
Konačna cena popravke
+ {{else if and .Nalog.CenaOd .Nalog.CenaDo}} +
+ {{formatBroj .Nalog.CenaOd}} + + {{formatBroj .Nalog.CenaDo}} din +
+
Procenjeni raspon cene
+ {{else if .Nalog.CenaOd}} +
+ od {{formatBroj .Nalog.CenaOd}} din +
+
Procenjena cena od
+ {{end}} +
+
+ {{end}} + + {{if .Nalog.Napomena}} +
+
Napomena
+
{{.Nalog.Napomena}}
+
+ {{end}} +
+ + + {{if .Telefon}} +
+
Kontakt
+ {{.Telefon}} + {{if .Adresa}}
{{.Adresa}}
{{end}} +
+ {{end}} + +
+ + diff --git a/web/templates/teme/podrazumevana/base.html b/web/templates/teme/podrazumevana/base.html index 370347e..1fcf1a6 100644 --- a/web/templates/teme/podrazumevana/base.html +++ b/web/templates/teme/podrazumevana/base.html @@ -351,8 +351,10 @@ redirect: 'follow' }).then(function(res) { var finUrl = new URL(res.url); - if (finUrl.search.indexOf('sacuvano') !== -1) { - // uspeh — prikaži toast, ostani na stranici + var isStiPath = finUrl.pathname === location.pathname; + var imaSacuvano = finUrl.search.indexOf('sacuvano') !== -1; + if (isStiPath && imaSacuvano) { + // uspeh na istoj stranici — prikaži toast, ostani window.ntechToast('Sačuvano', 'uspeh'); if (btn) btn.disabled = false; // odmah primeni podešavanja koja menjaju globalne atribute body-ja @@ -374,7 +376,7 @@ // promena teme zahteva reload (menja se ceo CSS fajl) if (f.querySelector('[name="lokalna_tema"]')) location.reload(); } else { - // greška ili redirect na drugu stranicu — navigiraj normalno + // redirect na drugu stranicu ili bez sacuvano — navigiraj normalno location.href = res.url; } }).catch(function() {