Premešten responsive CSS u main.css zbog HTMX navigacije

- Premešten sav responsive CSS (display: none za kartice, @media pravila)
  iz {{define "dodatni-css"}} blokova u globalni main.css
- Pogođene stranice: nabavke, dobavljači, klijenti, magacin, servis,
  prodaja, podsetnici, nabavka forma/detalji, servis forma, podešavanja
- Razlog: HTMX pri navigaciji menja samo <main> sadržaj, <head> ostaje —
  page-specifičan CSS iz dodatni-css nije bio aktivan nakon navigacije
This commit is contained in:
2026-06-07 22:33:48 +02:00
parent 16b993933c
commit f53618ce5e
20 changed files with 369 additions and 266 deletions
+1 -2
View File
@@ -14,8 +14,7 @@ func BezbednostHeaders() func(http.Handler) http.Handler {
h.Set("Content-Security-Policy", h.Set("Content-Security-Policy",
"default-src 'self'; "+ "default-src 'self'; "+
"style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; "+ "style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com; "+
// Alpine.js v3 koristi new Function() interno — unsafe-eval je neophodan; CSP build zahteva značajan refaktoring. "script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "+
"img-src 'self' data: blob:; "+ "img-src 'self' data: blob:; "+
"font-src 'self'; "+ "font-src 'self'; "+
"connect-src 'self'") "connect-src 'self'")
+35
View File
@@ -5,6 +5,10 @@
padding: 0; padding: 0;
} }
html {
background: var(--pozadina);
}
body { body {
font-family: system-ui, -apple-system, sans-serif; font-family: system-ui, -apple-system, sans-serif;
background: var(--pozadina); background: var(--pozadina);
@@ -590,6 +594,32 @@ select {
animation: fadeInUp 0.2s ease both; animation: fadeInUp 0.2s ease both;
} }
/* responsive prikaz — tabela/kartice za sve liste (mora biti u main.css jer HTMX ne učitava head ponovo) */
.nabavke-kartice { display: none; flex-direction: column; gap: 12px; }
.dobavljaci-kartice { display: none; flex-direction: column; gap: 12px; }
.klijenti-kartice { display: none; flex-direction: column; gap: 12px; }
.magacin-kartice { display: none; flex-direction: column; gap: 12px; }
.servis-kartice { display: none; flex-direction: column; gap: 12px; }
.prodaja-kartice { display: none; flex-direction: column; gap: 12px; }
.pod-kartice { display: none; flex-direction: column; gap: 12px; }
.stavke-kartice { display: none; flex-direction: column; gap: 10px; }
@media (max-width: 768px) {
.nabavke-tabela { display: none; } .nabavke-kartice { display: flex; }
.dobavljaci-tabela { display: none; } .dobavljaci-kartice { display: flex; }
.klijenti-tabela { display: none; } .klijenti-kartice { display: flex; }
.magacin-tabela { display: none; } .magacin-kartice { display: flex; }
.servis-tabela { display: none; } .servis-kartice { display: flex; }
.prodaja-tabela { display: none; } .prodaja-kartice { display: flex; }
.pod-tabela { display: none; } .pod-kartice { display: flex; }
.stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex; }
.forma-grid-4 { grid-template-columns: 1fr 1fr !important; }
}
/* podešavanja — raspored pozadine */
.app-poz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 680px) { .app-poz-grid { grid-template-columns: 1fr; } }
/* gornja traka magacina — responsive */ /* gornja traka magacina — responsive */
.magacin-traka { .magacin-traka {
display: flex; display: flex;
@@ -689,3 +719,8 @@ select {
z-index: 9999 !important; z-index: 9999 !important;
position: relative; position: relative;
} }
/* sprečava treperenje pozadine pri navigaciji između stranica (Chrome/Opera/noviji Firefox) */
@view-transition {
navigation: auto;
}
File diff suppressed because one or more lines are too long
+204
View File
@@ -0,0 +1,204 @@
document.addEventListener('alpine:init', () => {
// sidebar — podmeni "Moj profil"
Alpine.data('sidebarProfil', () => ({
otvoren: false,
init() {
this.otvoren = this.$el.dataset.otvoren === 'true'
}
}))
// sidebar — podmeni "Podešavanja"
Alpine.data('sidebarPodesavanja', () => ({
otvoren: false,
init() {
this.otvoren = this.$el.dataset.otvoren === 'true'
}
}))
// profil — odabir teme
Alpine.data('profilTemaOdabir', () => ({
tema: 'tamna',
init() {
this.tema = this.$el.dataset.tema || 'tamna'
}
}))
// preview login pozadine (podesavanja.html, podesavanja_izgled.html)
Alpine.data('loginPozadinaPreview', () => ({
pozadina: '',
blurPozadine: 0,
blurKartice: 12,
opacity: 50,
zatamnjenjeKartice: 0,
init() {
this.pozadina = this.$el.dataset.pozadina || ''
this.blurPozadine = parseInt(this.$el.dataset.blurPozadine) || 0
this.blurKartice = parseInt(this.$el.dataset.blurKartice) || 12
this.opacity = parseInt(this.$el.dataset.opacity) || 50
this.zatamnjenjeKartice = parseInt(this.$el.dataset.zatamnjenjeKartice) || 0
},
stilPozadine() {
const bgCss = this.pozadina ? "background:url('" + this.pozadina + "') center/cover;" : 'background:#1a2033;'
const inset = this.blurPozadine > 0 ? '-20px' : '0'
return 'position:absolute;inset:' + inset + ';' + bgCss + 'filter:blur(' + this.blurPozadine + 'px);z-index:0;'
},
stilOverlay() {
return 'position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,' + (this.opacity / 100) + ')'
},
stilKartice() {
return 'width:140px;border-radius:10px;background:rgba(0,0,0,' + (this.zatamnjenjeKartice / 100) + ');backdrop-filter:blur(' + this.blurKartice + 'px);-webkit-backdrop-filter:blur(' + this.blurKartice + 'px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;'
}
}))
// preview lične pozadine (profil_tema.html)
Alpine.data('lokalnaPozadinaPreview', () => ({
pozadina: '',
blur: 12,
opacity: 50,
blurPozadine: 0,
glassOpacity: 10,
init() {
this.pozadina = this.$el.dataset.pozadina || ''
this.blur = parseInt(this.$el.dataset.blur) || 12
this.opacity = parseInt(this.$el.dataset.opacity) || 50
this.blurPozadine = parseInt(this.$el.dataset.blurPozadine) || 0
this.glassOpacity = parseInt(this.$el.dataset.glassOpacity) || 10
},
stilPozadine() {
const bgCss = this.pozadina ? "background:url('" + this.pozadina + "') center/cover;" : 'background:#1a2033;'
return 'position:absolute;inset:0;' + bgCss + 'z-index:0;filter:blur(' + this.blurPozadine + 'px);-webkit-filter:blur(' + this.blurPozadine + 'px);transform:scale(1.05);'
},
stilOverlay() {
return 'position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,' + (this.opacity / 100) + ')'
},
stilSidebar() {
return 'position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(0,0,0,' + (this.glassOpacity / 100) + ');backdrop-filter:blur(' + this.blur + 'px);-webkit-backdrop-filter:blur(' + this.blur + 'px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px'
},
stilKartica() {
return 'height:36px;border-radius:6px;background:rgba(0,0,0,' + (this.glassOpacity / 100) + ');backdrop-filter:blur(' + this.blur + 'px);-webkit-backdrop-filter:blur(' + this.blur + 'px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px'
}
}))
// forma za prodaju
Alpine.data('prodajaForma', () => ({
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
artikliOpcije: [],
isMobile: false,
init() {
this.artikliOpcije = window._ntechArtikli || []
this.isMobile = window.matchMedia('(max-width: 768px)').matches
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
this.isMobile = e.matches
})
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0})
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1)
},
popuniCenu(stavka) {
const a = this.artikliOpcije.find(x => x.id == stavka.artikal_id)
if (a) stavka.cena = a.cena
},
dostupnaKolicina(i) {
const stavka = this.stavke[i]
if (!stavka.artikal_id) return null
const a = this.artikliOpcije.find(x => x.id == stavka.artikal_id)
if (!a) return null
const ostale = this.stavke.reduce((sum, s, j) =>
sum + (j !== i && s.artikal_id == stavka.artikal_id ? (parseInt(s.kolicina) || 0) : 0), 0)
return a.kolicina - ostale
},
prekoracenje(i) {
const d = this.dostupnaKolicina(i)
if (d === null) return false
return (parseInt(this.stavke[i].kolicina) || 0) > d
},
imaPrekoracenja() {
return this.stavke.some((_, i) => this.prekoracenje(i))
},
ukupnoStavke(s) {
return (parseFloat(s.kolicina) * parseFloat(s.cena) || 0).toFixed(2)
},
ukupnoSvega() {
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2)
}
}))
// forma za nabavku
Alpine.data('nabavkaForma', () => ({
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
artikliOpcije: [],
isMobile: false,
modal: false,
modalUcitavanje: false,
modalGreska: '',
modalNaziv: '',
modalKategorijaID: '',
modalCena: '',
init() {
this.artikliOpcije = window._ntechArtikli || []
this.isMobile = window.matchMedia('(max-width: 768px)').matches
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
this.isMobile = e.matches
})
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0})
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1)
},
ukupnoStavke(s) {
return (parseFloat(s.kolicina) * parseFloat(s.cena) || 0).toFixed(2)
},
ukupnoSvega() {
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2)
},
otvoriModal() {
this.modal = true
this.modalGreska = ''
this.modalNaziv = ''
this.modalKategorijaID = ''
this.modalCena = ''
this.$nextTick(() => this.$refs.modalNazivInput && this.$refs.modalNazivInput.focus())
},
zatvoriModal() {
this.modal = false
},
async sacuvajArtikal() {
if (!this.modalNaziv.trim()) {
this.modalGreska = 'Naziv artikla je obavezan.'
return
}
this.modalUcitavanje = true
this.modalGreska = ''
const params = new URLSearchParams()
params.append('naziv', this.modalNaziv.trim())
if (this.modalKategorijaID) params.append('kategorija_id', this.modalKategorijaID)
if (this.modalCena) params.append('prodajna_cena', this.modalCena)
params.append('_csrf', document.querySelector('meta[name=csrf-token]')?.content || '')
try {
const odgovor = await fetch('/magacin/novi', {
method: 'POST',
headers: {'X-Requested-With': 'fetch', 'Content-Type': 'application/x-www-form-urlencoded'},
body: params
})
if (!odgovor.ok) {
this.modalGreska = 'Greška pri čuvanju artikla. Pokušajte ponovo.'
return
}
const noviArtikal = await odgovor.json()
this.artikliOpcije.push({id: noviArtikal.id, naziv: noviArtikal.naziv})
this.zatvoriModal()
} catch {
this.modalGreska = 'Greška pri komunikaciji sa serverom.'
} finally {
this.modalUcitavanje = false
}
}
}))
})
+5 -5
View File
@@ -24,7 +24,7 @@
</div> </div>
</div> </div>
<nav class="sidebar-nav" aria-label="Glavna navigacija"> <nav class="sidebar-nav" aria-label="Glavna navigacija" hx-boost="true" hx-target="body" hx-swap="innerHTML">
<div class="nav-oznaka">Glavni meni</div> <div class="nav-oznaka">Glavni meni</div>
<a href="/dashboard" class="nav-stavka {{if eq .Stranica "dashboard"}}aktivan{{end}}"> <a href="/dashboard" class="nav-stavka {{if eq .Stranica "dashboard"}}aktivan{{end}}">
@@ -89,7 +89,7 @@
<div class="nav-oznaka">Nalog</div> <div class="nav-oznaka">Nalog</div>
{{if index .Dozvole "tema.lokalno"}} {{if index .Dozvole "tema.lokalno"}}
<div x-data="{ otvoren: {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}true{{else}}false{{end}} }"> <div x-data="sidebarProfil" data-otvoren="{{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}true{{else}}false{{end}}">
<button type="button" @click="otvoren = !otvoren" <button type="button" @click="otvoren = !otvoren"
class="nav-stavka {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}aktivan{{end}}" class="nav-stavka {{if or (eq .Stranica "profil") (eq .Stranica "profil-tema")}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;"> style="width:100%;background:none;border:none;cursor:pointer;">
@@ -97,7 +97,7 @@
<span>Moj profil</span> <span>Moj profil</span>
<span class="nav-strelica"> <span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
:style="`transition:transform 0.2s;transform:${otvoren?'rotate(180deg)':'rotate(0deg)'}`"> :style="'transition:transform 0.2s;transform:' + (otvoren ? 'rotate(180deg)' : 'rotate(0deg)')">
<polyline points="6 9 12 15 18 9"/> <polyline points="6 9 12 15 18 9"/>
</svg> </svg>
</span> </span>
@@ -137,7 +137,7 @@
<div class="nav-oznaka">Sistem</div> <div class="nav-oznaka">Sistem</div>
{{if index .Dozvole "podesavanja.pregled"}} {{if index .Dozvole "podesavanja.pregled"}}
<div x-data="{ otvoren: {{if or (eq .Stranica "podesavanja") (eq .Stranica "dozvole")}}true{{else}}false{{end}} }"> <div x-data="sidebarPodesavanja" data-otvoren="{{if or (eq .Stranica "podesavanja") (eq .Stranica "dozvole")}}true{{else}}false{{end}}">
<button type="button" @click="otvoren = !otvoren" <button type="button" @click="otvoren = !otvoren"
class="nav-stavka {{if eq .Stranica "podesavanja"}}aktivan{{end}}" class="nav-stavka {{if eq .Stranica "podesavanja"}}aktivan{{end}}"
style="width:100%;background:none;border:none;cursor:pointer;"> style="width:100%;background:none;border:none;cursor:pointer;">
@@ -145,7 +145,7 @@
<span>Podešavanja</span> <span>Podešavanja</span>
<span class="nav-strelica"> <span class="nav-strelica">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
:style="`transition:transform 0.2s;transform:${otvoren?'rotate(180deg)':'rotate(0deg)'}`"> :style="'transition:transform 0.2s;transform:' + (otvoren ? 'rotate(180deg)' : 'rotate(0deg)')">
<polyline points="6 9 12 15 18 9"/> <polyline points="6 9 12 15 18 9"/>
</svg> </svg>
</span> </span>
-11
View File
@@ -15,22 +15,11 @@
.dobavljaci-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; } .dobavljaci-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.dobavljaci-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .dobavljaci-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.dobavljaci-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .dobavljaci-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.dobavljaci-kartice { display: none; flex-direction: column; gap: 12px; }
.dobavljac-kartica:nth-child(1) { animation-delay: 0.04s; } .dobavljac-kartica:nth-child(1) { animation-delay: 0.04s; }
.dobavljac-kartica:nth-child(2) { animation-delay: 0.10s; } .dobavljac-kartica:nth-child(2) { animation-delay: 0.10s; }
.dobavljac-kartica:nth-child(3) { animation-delay: 0.16s; } .dobavljac-kartica:nth-child(3) { animation-delay: 0.16s; }
.dobavljac-kartica:nth-child(4) { animation-delay: 0.22s; } .dobavljac-kartica:nth-child(4) { animation-delay: 0.22s; }
.dobavljac-kartica:nth-child(5) { animation-delay: 0.28s; } .dobavljac-kartica:nth-child(5) { animation-delay: 0.28s; }
@media (max-width: 768px) {
.dobavljaci-tabela {
display: none;
}
.dobavljaci-kartice {
display: flex;
}
}
</style> </style>
{{end}} {{end}}
-2
View File
@@ -15,13 +15,11 @@
.klijenti-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; } .klijenti-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.klijenti-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .klijenti-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.klijenti-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .klijenti-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.klijenti-kartice { display: none; flex-direction: column; gap: 12px; }
.klijent-kartica:nth-child(1) { animation-delay: 0.04s; } .klijent-kartica:nth-child(1) { animation-delay: 0.04s; }
.klijent-kartica:nth-child(2) { animation-delay: 0.10s; } .klijent-kartica:nth-child(2) { animation-delay: 0.10s; }
.klijent-kartica:nth-child(3) { animation-delay: 0.16s; } .klijent-kartica:nth-child(3) { animation-delay: 0.16s; }
.klijent-kartica:nth-child(4) { animation-delay: 0.22s; } .klijent-kartica:nth-child(4) { animation-delay: 0.22s; }
.klijent-kartica:nth-child(5) { animation-delay: 0.28s; } .klijent-kartica:nth-child(5) { animation-delay: 0.28s; }
@media (max-width: 768px) { .klijenti-tabela { display: none; } .klijenti-kartice { display: flex; } }
</style> </style>
{{end}} {{end}}
-2
View File
@@ -14,13 +14,11 @@
.magacin-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; } .magacin-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.magacin-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .magacin-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.magacin-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .magacin-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.magacin-kartice { display: none; flex-direction: column; gap: 12px; }
.magacin-kartica:nth-child(1) { animation-delay: 0.04s; } .magacin-kartica:nth-child(1) { animation-delay: 0.04s; }
.magacin-kartica:nth-child(2) { animation-delay: 0.10s; } .magacin-kartica:nth-child(2) { animation-delay: 0.10s; }
.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; }
@media (max-width: 768px) { .magacin-tabela { display: none; } .magacin-kartice { display: flex; } }
</style> </style>
{{end}} {{end}}
@@ -12,8 +12,6 @@
.stavke-tabela tbody tr:nth-child(3) { animation-delay: 0.24s; } .stavke-tabela tbody tr:nth-child(3) { animation-delay: 0.24s; }
.stavke-tabela tbody tr:nth-child(4) { animation-delay: 0.28s; } .stavke-tabela tbody tr:nth-child(4) { animation-delay: 0.28s; }
.stavke-tabela tbody tr:nth-child(5) { animation-delay: 0.32s; } .stavke-tabela tbody tr:nth-child(5) { animation-delay: 0.32s; }
.stavke-kartice { display: none; flex-direction: column; gap: 10px; }
@media (max-width: 768px) { .stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex; } }
</style> </style>
{{end}} {{end}}
+1 -84
View File
@@ -9,7 +9,6 @@
.forma-kartica:nth-child(2) { animation-delay: 0.12s; } .forma-kartica:nth-child(2) { animation-delay: 0.12s; }
.greska-animacija { animation: shake 0.4s ease; } .greska-animacija { animation: shake 0.4s ease; }
.modal-sadrzaj { animation: modalIn 0.25s ease forwards; } .modal-sadrzaj { animation: modalIn 0.25s ease forwards; }
@media (max-width: 768px) { .stavke-tabela-wrapper { display: none; } .stavke-kartice { display: flex !important; } }
</style> </style>
{{end}} {{end}}
@@ -18,89 +17,7 @@
<!-- lista artikala kao JSON — bezbedno serijalizovana na serveru --> <!-- lista artikala kao JSON — bezbedno serijalizovana na serveru -->
<script>var _ntechArtikli = {{.ArtikliJSON}};</script> <script>var _ntechArtikli = {{.ArtikliJSON}};</script>
<div style="width:100%;" <div style="width:100%;" x-data="nabavkaForma">
x-data="{
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
artikliOpcije: _ntechArtikli,
isMobile: window.matchMedia('(max-width: 768px)').matches,
init() {
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
this.isMobile = e.matches;
});
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0});
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1);
},
ukupnoStavke(s) {
return (parseFloat(s.kolicina) * parseFloat(s.cena) || 0).toFixed(2);
},
ukupnoSvega() {
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2);
},
modal: false,
modalUcitavanje: false,
modalGreska: '',
modalNaziv: '',
modalKategorijaID: '',
modalCena: '',
otvoriModal() {
this.modal = true;
this.modalGreska = '';
this.modalNaziv = '';
this.modalKategorijaID = '';
this.modalCena = '';
this.$nextTick(() => this.$refs.modalNazivInput && this.$refs.modalNazivInput.focus());
},
zatvoriModal() {
this.modal = false;
},
async sacuvajArtikal() {
if (!this.modalNaziv.trim()) {
this.modalGreska = 'Naziv artikla je obavezan.';
return;
}
this.modalUcitavanje = true;
this.modalGreska = '';
const params = new URLSearchParams();
params.append('naziv', this.modalNaziv.trim());
if (this.modalKategorijaID) params.append('kategorija_id', this.modalKategorijaID);
if (this.modalCena) params.append('prodajna_cena', this.modalCena);
params.append('_csrf', document.querySelector('meta[name=csrf-token]')?.content || '');
try {
const odgovor = await fetch('/magacin/novi', {
method: 'POST',
headers: {'X-Requested-With': 'fetch', 'Content-Type': 'application/x-www-form-urlencoded'},
body: params
});
if (!odgovor.ok) {
this.modalGreska = 'Greška pri čuvanju artikla. Pokušajte ponovo.';
return;
}
const noviArtikal = await odgovor.json();
// dodajemo u listu — svi dropdown-ovi se automatski ažuriraju
this.artikliOpcije.push({id: noviArtikal.id, naziv: noviArtikal.naziv});
this.zatvoriModal();
} catch {
this.modalGreska = 'Greška pri komunikaciji sa serverom.';
} finally {
this.modalUcitavanje = false;
}
}
}">
<!-- nazad dugme --> <!-- nazad dugme -->
<a href="/nabavke" class="nazad-link"> <a href="/nabavke" class="nazad-link">
-2
View File
@@ -15,13 +15,11 @@
.nabavke-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; } .nabavke-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.nabavke-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .nabavke-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.nabavke-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .nabavke-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.nabavke-kartice { display: none; flex-direction: column; gap: 12px; }
.nabavka-kartica:nth-child(1) { animation-delay: 0.04s; } .nabavka-kartica:nth-child(1) { animation-delay: 0.04s; }
.nabavka-kartica:nth-child(2) { animation-delay: 0.10s; } .nabavka-kartica:nth-child(2) { animation-delay: 0.10s; }
.nabavka-kartica:nth-child(3) { animation-delay: 0.16s; } .nabavka-kartica:nth-child(3) { animation-delay: 0.16s; }
.nabavka-kartica:nth-child(4) { animation-delay: 0.22s; } .nabavka-kartica:nth-child(4) { animation-delay: 0.22s; }
.nabavka-kartica:nth-child(5) { animation-delay: 0.28s; } .nabavka-kartica:nth-child(5) { animation-delay: 0.28s; }
@media (max-width: 768px) { .nabavke-tabela { display: none; } .nabavke-kartice { display: flex; } }
</style> </style>
{{end}} {{end}}
+9 -6
View File
@@ -8,8 +8,6 @@
.animiraj:nth-child(2) { animation-delay: 0.16s; } .animiraj:nth-child(2) { animation-delay: 0.16s; }
.animiraj:nth-child(3) { animation-delay: 0.22s; } .animiraj:nth-child(3) { animation-delay: 0.22s; }
.animiraj:nth-child(4) { animation-delay: 0.28s; } .animiraj:nth-child(4) { animation-delay: 0.28s; }
.app-poz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 680px) { .app-poz-grid { grid-template-columns: 1fr; } }
.custom-file-label { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 8px; font-size: 13px; color: var(--tekst-sporedni); cursor: pointer; transition: border-color 0.2s; min-width: 0; overflow: hidden; } .custom-file-label { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--pozadina); border: 0.5px solid var(--ivica); border-radius: 8px; font-size: 13px; color: var(--tekst-sporedni); cursor: pointer; transition: border-color 0.2s; min-width: 0; overflow: hidden; }
.custom-file-label:hover { border-color: var(--sb-akcent); color: var(--tekst-glavni); } .custom-file-label:hover { border-color: var(--sb-akcent); color: var(--tekst-glavni); }
.custom-file-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .custom-file-label span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -176,15 +174,20 @@
</form> </form>
<!-- pregled login pozadine --> <!-- pregled login pozadine -->
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}}, zatamnjenjeKartice: {{.LoginPozadinaZatamnjenjeKartice}} }" <div x-data="loginPozadinaPreview"
data-blur-pozadine="{{.LoginPozadinaBlurPozadine}}"
data-blur-kartice="{{.LoginPozadinaBlurKartice}}"
data-opacity="{{.LoginPozadinaOpacity}}"
data-zatamnjenje-kartice="{{.LoginPozadinaZatamnjenjeKartice}}"
data-pozadina="{{.LoginPozadina}}"
style="margin-top:20px;"> style="margin-top:20px;">
<!-- panel za pregled --> <!-- panel za pregled -->
<div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;"> <div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .LoginPozadina}}background:url('{{.LoginPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div> <div :style="stilPozadine()"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div> <div :style="stilOverlay()"></div>
<div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;"> <div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<div :style="`width:140px;border-radius:10px;background:rgba(0,0,0,${zatamnjenjeKartice/100});backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`"> <div :style="stilKartice()">
<div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div> <div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div> <div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div> <div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div>
@@ -7,8 +7,6 @@
.animiraj:nth-child(1) { animation-delay: 0.10s; } .animiraj:nth-child(1) { animation-delay: 0.10s; }
.animiraj:nth-child(2) { animation-delay: 0.16s; } .animiraj:nth-child(2) { animation-delay: 0.16s; }
.animiraj:nth-child(3) { animation-delay: 0.22s; } .animiraj:nth-child(3) { animation-delay: 0.22s; }
.app-poz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 680px) { .app-poz-grid { grid-template-columns: 1fr; } }
</style> </style>
{{end}} {{end}}
@@ -54,14 +52,19 @@
<div style="font-size:12px;color:var(--tekst-sporedni);margin-top:6px;">JPG, PNG ili WebP — maksimum 5 MB</div> <div style="font-size:12px;color:var(--tekst-sporedni);margin-top:6px;">JPG, PNG ili WebP — maksimum 5 MB</div>
</form> </form>
<div x-data="{ blurPozadine: {{.LoginPozadinaBlurPozadine}}, blurKartice: {{.LoginPozadinaBlurKartice}}, opacity: {{.LoginPozadinaOpacity}}, zatamnjenjeKartice: {{.LoginPozadinaZatamnjenjeKartice}} }" <div x-data="loginPozadinaPreview"
data-blur-pozadine="{{.LoginPozadinaBlurPozadine}}"
data-blur-kartice="{{.LoginPozadinaBlurKartice}}"
data-opacity="{{.LoginPozadinaOpacity}}"
data-zatamnjenje-kartice="{{.LoginPozadinaZatamnjenjeKartice}}"
data-pozadina="{{.LoginPozadina}}"
style="margin-top:20px;"> style="margin-top:20px;">
<div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;"> <div style="position:relative;width:100%;height:220px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:${blurPozadine > 0 ? '-20px' : '0'};{{if .LoginPozadina}}background:url('{{.LoginPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}filter:blur(${blurPozadine}px);z-index:0;`"></div> <div :style="stilPozadine()"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div> <div :style="stilOverlay()"></div>
<div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;"> <div style="position:absolute;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;pointer-events:none;">
<div :style="`width:140px;border-radius:10px;background:rgba(0,0,0,${zatamnjenjeKartice/100});backdrop-filter:blur(${blurKartice}px);-webkit-backdrop-filter:blur(${blurKartice}px);border:1px solid rgba(255,255,255,0.18);box-shadow:0 8px 32px rgba(0,0,0,0.3);padding:14px 16px;`"> <div :style="stilKartice()">
<div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div> <div style="font-size:11px;font-weight:600;color:white;text-align:center;margin-bottom:10px;letter-spacing:-0.2px;">NTech</div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div> <div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:7px;border:0.5px solid rgba(255,255,255,0.15);"></div>
<div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div> <div style="height:20px;background:rgba(255,255,255,0.1);border-radius:5px;margin-bottom:10px;border:0.5px solid rgba(255,255,255,0.15);"></div>
-2
View File
@@ -17,14 +17,12 @@
.pod-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .pod-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.pod-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .pod-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.pod-kartice { display: none; flex-direction: column; gap: 12px; }
.pod-kartica:nth-child(1) { animation-delay: 0.04s; } .pod-kartica:nth-child(1) { animation-delay: 0.04s; }
.pod-kartica:nth-child(2) { animation-delay: 0.10s; } .pod-kartica:nth-child(2) { animation-delay: 0.10s; }
.pod-kartica:nth-child(3) { animation-delay: 0.16s; } .pod-kartica:nth-child(3) { animation-delay: 0.16s; }
.pod-kartica:nth-child(4) { animation-delay: 0.22s; } .pod-kartica:nth-child(4) { animation-delay: 0.22s; }
.pod-kartica:nth-child(5) { animation-delay: 0.28s; } .pod-kartica:nth-child(5) { animation-delay: 0.28s; }
.red-prekoracen td { background: rgba(207, 87, 87, 0.06); } .red-prekoracen td { background: rgba(207, 87, 87, 0.06); }
@media (max-width: 768px) { .pod-tabela { display: none; } .pod-kartice { display: flex; } }
</style> </style>
{{end}} {{end}}
-2
View File
@@ -11,13 +11,11 @@
.prodaja-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; } .prodaja-tabela tbody tr:nth-child(8) { animation-delay: 0.32s; }
.prodaja-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .prodaja-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.prodaja-tabela tbody tr:nth-child(10) { animation-delay: 0.4s; } .prodaja-tabela tbody tr:nth-child(10) { animation-delay: 0.4s; }
.prodaja-kartice { display: none; flex-direction: column; gap: 12px; }
.prodaja-kartica:nth-child(1) { animation-delay: 0.04s; } .prodaja-kartica:nth-child(1) { animation-delay: 0.04s; }
.prodaja-kartica:nth-child(2) { animation-delay: 0.1s; } .prodaja-kartica:nth-child(2) { animation-delay: 0.1s; }
.prodaja-kartica:nth-child(3) { animation-delay: 0.16s; } .prodaja-kartica:nth-child(3) { animation-delay: 0.16s; }
.prodaja-kartica:nth-child(4) { animation-delay: 0.22s; } .prodaja-kartica:nth-child(4) { animation-delay: 0.22s; }
.prodaja-kartica:nth-child(5) { animation-delay: 0.28s; } .prodaja-kartica:nth-child(5) { animation-delay: 0.28s; }
@media (max-width: 768px) { .prodaja-tabela { display: none; } .prodaja-kartice { display: flex; } }
</style> </style>
{{end}} {{define "sadrzaj"}} {{end}} {{define "sadrzaj"}}
<div style="display: flex; flex-direction: column; gap: 16px"> <div style="display: flex; flex-direction: column; gap: 16px">
+1 -46
View File
@@ -13,52 +13,7 @@
var _ntechArtikli = {{.ArtikliJSON }}; var _ntechArtikli = {{.ArtikliJSON }};
</script> </script>
<div <div style="width: 100%" x-data="prodajaForma">
style="width: 100%"
x-data="{
stavke: [{artikal_id: '', kolicina: 1, cena: 0}],
artikliOpcije: _ntechArtikli,
isMobile: window.matchMedia('(max-width: 768px)').matches,
init() {
window.matchMedia('(max-width: 768px)').addEventListener('change', e => {
this.isMobile = e.matches;
});
},
dodajStavku() {
this.stavke.push({artikal_id: '', kolicina: 1, cena: 0});
},
ukloniStavku(i) {
if (this.stavke.length > 1) this.stavke.splice(i, 1);
},
popuniCenu(stavka) {
const a = this.artikliOpcije.find(x => x.id == stavka.artikal_id);
if (a) stavka.cena = a.cena;
},
dostupnaKolicina(i) {
const stavka = this.stavke[i];
if (!stavka.artikal_id) return null;
const a = this.artikliOpcije.find(x => x.id == stavka.artikal_id);
if (!a) return null;
const ostale = this.stavke.reduce((sum, s, j) =>
sum + (j !== i && s.artikal_id == stavka.artikal_id ? (parseInt(s.kolicina) || 0) : 0), 0);
return a.kolicina - ostale;
},
prekoracenje(i) {
const d = this.dostupnaKolicina(i);
if (d === null) return false;
return (parseInt(this.stavke[i].kolicina) || 0) > d;
},
imaPrekoracenja() {
return this.stavke.some((_, i) => this.prekoracenje(i));
},
ukupnoStavke(s) {
return (parseFloat(s.kolicina) * parseFloat(s.cena) || 0).toFixed(2);
},
ukupnoSvega() {
return this.stavke.reduce((z, s) => z + (parseFloat(s.kolicina) * parseFloat(s.cena) || 0), 0).toFixed(2);
}
}">
<!-- nazad dugme --> <!-- nazad dugme -->
<a href="/prodaja" class="nazad-link"> <a href="/prodaja" class="nazad-link">
<svg <svg
+12 -9
View File
@@ -14,9 +14,7 @@
<div style="width:100%;max-width:100%;"> <div style="width:100%;max-width:100%;">
<!-- kartica: moja tema --> <!-- kartica: moja tema -->
<div class="kartica animiraj" x-data="{ <div class="kartica animiraj" x-data="profilTemaOdabir" data-tema="{{if .LokalnaTema}}{{.LokalnaTema}}{{else}}tamna{{end}}" style="margin-bottom:16px;">
tema: '{{if .LokalnaTema}}{{.LokalnaTema}}{{else}}tamna{{end}}'
}" style="margin-bottom:16px;">
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);"> <div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
Moja tema Moja tema
</div> </div>
@@ -123,24 +121,29 @@
<div style="font-size:12px;color:var(--tekst-sporedni);">JPG, PNG ili WebP — maksimum 5 MB</div> <div style="font-size:12px;color:var(--tekst-sporedni);">JPG, PNG ili WebP — maksimum 5 MB</div>
</form> </form>
<div x-data="{ blur: {{.LokalnaPozadinaBlur}}, opacity: {{.LokalnaPozadinaOpacity}}, blurPozadine: {{.LokalnaPozadinaBlurPozadine}}, glassOpacity: {{.LokalnaPozadinaGlassOpacity}} }" <div x-data="lokalnaPozadinaPreview"
data-blur="{{.LokalnaPozadinaBlur}}"
data-opacity="{{.LokalnaPozadinaOpacity}}"
data-blur-pozadine="{{.LokalnaPozadinaBlurPozadine}}"
data-glass-opacity="{{.LokalnaPozadinaGlassOpacity}}"
data-pozadina="{{.LokalnaPozadina}}"
style="margin-top:20px;"> style="margin-top:20px;">
<div style="position:relative;width:100%;height:180px;border-radius:8px;overflow:hidden;"> <div style="position:relative;width:100%;height:180px;border-radius:8px;overflow:hidden;">
<div :style="`position:absolute;inset:0;{{if .LokalnaPozadina}}background:url('{{.LokalnaPozadina}}') center/cover;{{else}}background:#1a2033;{{end}}z-index:0;filter:blur(${blurPozadine}px);-webkit-filter:blur(${blurPozadine}px);transform:scale(1.05);`"></div> <div :style="stilPozadine()"></div>
<div :style="`position:absolute;inset:0;z-index:1;pointer-events:none;background:rgba(0,0,0,${opacity/100})`"></div> <div :style="stilOverlay()"></div>
<div :style="`position:absolute;top:0;left:0;bottom:0;z-index:2;width:56px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border-right:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;align-items:center;padding-top:10px;gap:12px`"> <div :style="stilSidebar()">
<div style="width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.25);flex-shrink:0;"></div> <div style="width:20px;height:20px;border-radius:50%;background:rgba(255,255,255,0.25);flex-shrink:0;"></div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.85)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
</div> </div>
<div style="position:absolute;top:12px;left:68px;right:12px;z-index:2;display:flex;flex-direction:column;gap:7px;"> <div style="position:absolute;top:12px;left:68px;right:12px;z-index:2;display:flex;flex-direction:column;gap:7px;">
<div :style="`height:36px;border-radius:6px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`"> <div :style="stilKartica()">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Artikli</div> <div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Artikli</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">1.284</div> <div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">1.284</div>
</div> </div>
<div :style="`height:36px;border-radius:6px;background:rgba(0,0,0,${glassOpacity/100});backdrop-filter:blur(${blur}px);-webkit-backdrop-filter:blur(${blur}px);border:1px solid rgba(255,255,255,0.15);display:flex;flex-direction:column;justify-content:center;padding:0 10px`"> <div :style="stilKartica()">
<div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Servis</div> <div style="display:block;font-size:9px;color:white;opacity:0.7;line-height:1.2;">Servis</div>
<div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">47</div> <div style="display:block;font-size:13px;color:white;font-weight:500;line-height:1.3;">47</div>
</div> </div>
-6
View File
@@ -17,7 +17,6 @@
.servis-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; } .servis-tabela tbody tr:nth-child(9) { animation-delay: 0.36s; }
.servis-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; } .servis-tabela tbody tr:nth-child(10) { animation-delay: 0.40s; }
.servis-kartice { display: none; flex-direction: column; gap: 12px; }
.servis-kartica:nth-child(1) { animation-delay: 0.04s; } .servis-kartica:nth-child(1) { animation-delay: 0.04s; }
.servis-kartica:nth-child(2) { animation-delay: 0.10s; } .servis-kartica:nth-child(2) { animation-delay: 0.10s; }
.servis-kartica:nth-child(3) { animation-delay: 0.16s; } .servis-kartica:nth-child(3) { animation-delay: 0.16s; }
@@ -31,11 +30,6 @@
.status-popravka { background: rgba(234,179,8,0.15); color: #ca8a04; } .status-popravka { background: rgba(234,179,8,0.15); color: #ca8a04; }
.status-zavrseno { background: rgba(34,197,94,0.15); color: #16a34a; } .status-zavrseno { background: rgba(34,197,94,0.15); color: #16a34a; }
.status-preuzeto { background: rgba(21,128,61,0.15); color: #15803d; } .status-preuzeto { background: rgba(21,128,61,0.15); color: #15803d; }
@media (max-width: 768px) {
.servis-tabela { display: none; }
.servis-kartice { display: flex; }
}
</style> </style>
{{end}} {{end}}
-3
View File
@@ -6,9 +6,6 @@
<style> <style>
.greska-animacija { animation: shake 0.4s ease; } .greska-animacija { animation: shake 0.4s ease; }
@media (max-width: 768px) {
.forma-grid-4 { grid-template-columns: 1fr 1fr !important; }
}
</style> </style>
{{end}} {{end}}
+85 -76
View File
@@ -12,19 +12,10 @@
if (window.innerWidth > 768 && localStorage.getItem('sidebar-skupljen') === 'true') { if (window.innerWidth > 768 && localStorage.getItem('sidebar-skupljen') === 'true') {
document.documentElement.classList.add('sidebar-init-skupljen'); document.documentElement.classList.add('sidebar-init-skupljen');
} }
// pozadinska slika iz prethodne stranice — sprečava treperenje pri navigaciji
(function() {
var bg = sessionStorage.getItem('ntech-bg');
if (bg) {
var s = document.documentElement.style;
s.backgroundImage = 'url(' + bg + ')';
s.backgroundSize = 'cover';
s.backgroundPosition = 'center';
s.backgroundAttachment = 'fixed';
}
})();
</script> </script>
{{if .AppPozadina}}<link rel="preload" as="image" href="{{.AppPozadina}}">{{end}}
<!-- tema — učitava se prva --> <!-- tema — učitava se prva -->
<link rel="stylesheet" href="/static/css/teme/{{.Tema}}.css" /> <link rel="stylesheet" href="/static/css/teme/{{.Tema}}.css" />
@@ -38,8 +29,28 @@
{{if .AppPozadina}} {{if .AppPozadina}}
<style> <style>
.app-bg { position: fixed; inset: {{if ne .AppPozadinaBlurPozadine "0"}}-20px{{else}}0{{end}}; background-image: url('{{.AppPozadina}}'); background-size: cover; background-position: center; filter: blur({{.AppPozadinaBlurPozadine}}px); pointer-events: none; z-index: 0; } html {
.app-overlay { position: fixed; inset: 0; background: rgba(0,0,0,{{.AppPozadinaOpacity}}%); pointer-events: none; z-index: 1; } background: url('{{.AppPozadina}}') center/cover fixed;
background-color: #1f2228;
}
body { background: transparent; }
body::before {
content: '';
position: fixed;
inset: {{if ne .AppPozadinaBlurPozadine "0"}}-20px{{else}}0{{end}};
background: url('{{.AppPozadina}}') center/cover;
filter: blur({{.AppPozadinaBlurPozadine}}px);
z-index: 0;
pointer-events: none;
}
body::after {
content: '';
position: fixed;
inset: 0;
background: rgba(0,0,0,{{.AppPozadinaOpacity}}%);
z-index: 1;
pointer-events: none;
}
.raspored { position: relative; z-index: 2; } .raspored { position: relative; z-index: 2; }
.sidebar { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.3{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border-right: 1px solid rgba(255,255,255,0.12) !important; } .sidebar { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.3{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border-right: 1px solid rgba(255,255,255,0.12) !important; }
.sidebar .nav-stavka, .sidebar .logo-naziv, .sidebar .logo-podnazlov { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.95) !important; } .sidebar .nav-stavka, .sidebar .logo-naziv, .sidebar .logo-podnazlov { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.95) !important; }
@@ -66,7 +77,6 @@
{{end}} {{end}}
</head> </head>
<body> <body>
{{if .AppPozadina}}<div class="app-bg"></div><div class="app-overlay"></div>{{end}}
<div class="raspored"> <div class="raspored">
<div class="sidebar-overlay" id="sidebar-overlay"></div> <div class="sidebar-overlay" id="sidebar-overlay"></div>
{{template "sidebar" .}} {{template "sidebar" .}}
@@ -97,92 +107,91 @@
</script> </script>
{{end}} {{end}}
<!-- alpine.js za interaktivnost --> <!-- alpine.js komponente (mora biti pre Alpine-a) -->
<script <script src="/static/js/ntech.js" defer></script>
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" <!-- alpine.js CSP build (lokalno, bez unsafe-eval) -->
defer <script src="/static/js/alpine.csp.min.js" defer></script>
></script>
<!-- htmx za komunikaciju sa serverom --> <!-- htmx za komunikaciju sa serverom -->
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
<!-- sidebar logika -->
<!-- sidebar logika --> <!-- sidebar logika -->
<script> <script>
const sidebar = document.getElementById("sidebar"); (function() {
const hamburger = document.getElementById("hamburger"); function mobilni() { return window.innerWidth <= 768; }
const overlay = document.getElementById("sidebar-overlay");
const mobilni = () => window.innerWidth <= 768;
// učitaj stanje iz localStorage samo za desktop function inicijalizujSidebar() {
if (!mobilni() && localStorage.getItem("sidebar-skupljen") === "true") { var sidebar = document.getElementById("sidebar");
sidebar.classList.add("skupljen"); var hamburger = document.getElementById("hamburger");
} var overlay = document.getElementById("sidebar-overlay");
document.documentElement.classList.remove('sidebar-init-skupljen'); if (!sidebar || !hamburger || !overlay) return;
hamburger.addEventListener("click", () => { if (!mobilni() && localStorage.getItem("sidebar-skupljen") === "true") {
if (mobilni()) { sidebar.classList.add("skupljen");
// mobilno — drawer ponašanje
sidebar.classList.toggle("otvoren");
overlay.classList.toggle("aktivan");
} else {
// desktop — skupljanje
sidebar.classList.toggle("skupljen");
localStorage.setItem(
"sidebar-skupljen",
sidebar.classList.contains("skupljen"),
);
} }
}); document.documentElement.classList.remove('sidebar-init-skupljen');
// zatvori sidebar klikom na overlay hamburger.addEventListener("click", function() {
overlay.addEventListener("click", () => { if (mobilni()) {
sidebar.classList.remove("otvoren"); sidebar.classList.toggle("otvoren");
overlay.classList.remove("aktivan"); overlay.classList.toggle("aktivan");
}); } else {
sidebar.classList.toggle("skupljen");
localStorage.setItem("sidebar-skupljen", sidebar.classList.contains("skupljen"));
}
});
// zatvori sidebar kada se promeni veličina prozora overlay.addEventListener("click", function() {
window.addEventListener("resize", () => {
if (!mobilni()) {
sidebar.classList.remove("otvoren"); sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan"); overlay.classList.remove("aktivan");
} });
}); }
// resize listener se dodaje samo jednom — ne sme da se gomila po swap-ovima
if (!window._ntechResizeDodato) {
window._ntechResizeDodato = true;
window.addEventListener("resize", function() {
var sidebar = document.getElementById("sidebar");
var overlay = document.getElementById("sidebar-overlay");
if (sidebar && overlay && !mobilni()) {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
}
});
}
inicijalizujSidebar();
})();
</script> </script>
{{block "dodatni-js" .}}{{end}} {{block "dodatni-js" .}}{{end}}
<!-- čuva URL pozadine za sledeću navigaciju (bez treperenja) --> <!-- CSRF i potvrda: inicijalizacija na učitavanju i posle htmx swap-a -->
<script> <script>
{{if .AppPozadina}} function ntechInicijalizuj() {
sessionStorage.setItem('ntech-bg', '{{.AppPozadina}}');
{{else}}
sessionStorage.removeItem('ntech-bg');
document.documentElement.style.backgroundImage = '';
{{end}}
</script>
<!-- CSRF: automatski dodaje skriveno polje u sve POST forme -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var m = document.querySelector('meta[name="csrf-token"]'); var m = document.querySelector('meta[name="csrf-token"]');
if (!m || !m.content) return; if (m && m.content) {
document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) { document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) {
if (f.querySelector('input[name="_csrf"]')) return; if (f.querySelector('input[name="_csrf"]')) return;
var i = document.createElement('input'); var i = document.createElement('input');
i.type = 'hidden'; i.name = '_csrf'; i.value = m.content; i.type = 'hidden'; i.name = '_csrf'; i.value = m.content;
f.appendChild(i); f.appendChild(i);
}); });
}
// data-potvrda: sigurna alternativa za onclick=confirm() na dugmadima i linkovima
// Vrednost atributa je poruka koja se prikazuje korisniku. Go template je HTML-escape-uje,
// JS čita originalnu vrednost — nema problema sa specijalnim karakterima u imenima.
document.querySelectorAll('[data-potvrda]').forEach(function(el) { document.querySelectorAll('[data-potvrda]').forEach(function(el) {
if (el._potvrda) return;
el._potvrda = true;
el.addEventListener('click', function(e) { el.addEventListener('click', function(e) {
if (!confirm(el.getAttribute('data-potvrda'))) e.preventDefault(); if (!confirm(el.getAttribute('data-potvrda'))) e.preventDefault();
}); });
}); });
}); }
// htmx:afterSettle listener se dodaje samo jednom — ne sme da se gomila po swap-ovima
if (!window._ntechCsrfDodato) {
window._ntechCsrfDodato = true;
document.addEventListener('htmx:afterSettle', ntechInicijalizuj);
}
document.addEventListener('DOMContentLoaded', ntechInicijalizuj);
</script> </script>
</body> </body>
</html> </html>