Files
GoNtech/web/templates/teme/podrazumevana/base.html
T
Dasko 5f017fd7ed Servis: pregled troškova, auto-cena delova, modalni prozor za potvrdu
- Detalji naloga prikazuju cenu usluge, ugrađene delove, ukupno i za naplatu kao zasebne stavke
- Otpremnica uključuje stavku ugrađenih delova u obračun
- Biranje artikla u formi za delove automatski popunjava cenu po komadu
- Zamenjen confirm() sa prilagođenim modalnim prozorom za sve potvrde
2026-06-20 00:40:29 +02:00

309 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "base"}}
<!doctype html>
<html lang="sr">
<head>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{block "naslov" .}}NTech{{end}}</title>
<meta name="csrf-token" content="{{.CsrfToken}}">
<script>
if (window.innerWidth > 768 && localStorage.getItem('sidebar-skupljen') === 'true') {
document.documentElement.classList.add('sidebar-init-skupljen');
}
</script>
<!-- tema — učitava se prva -->
<link rel="stylesheet" href="/static/css/teme/{{.Tema}}.css?v={{.AssetV}}" />
<!-- glavni stilovi -->
<link rel="stylesheet" href="/static/css/main.css?v={{.AssetV}}" />
{{block "dodatni-css" .}}{{end}}
{{if .AppPozadina}}<link rel="preload" as="image" href="{{.AppPozadina}}">{{end}}
{{if .AppPozadina}}
<style>
html {
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; }
.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 svg { color: rgba(255,255,255,0.95) !important; stroke: rgba(255,255,255,0.95) !important; }
.sidebar .nav-oznaka { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.7) !important; }
.topbar { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border-bottom: 1px solid rgba(255,255,255,0.12) !important; }
.topbar-naslov { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
.kartica, .premesti-modal { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border: 1px solid rgba(255,255,255,0.12) !important; }
body:not([data-hover]) .kartica:hover { background: rgba(0,0,0,0.38) !important; }
body:not([data-hover]) .kartica:hover .dash-ikona { filter: brightness(0.55); }
.kartica p, .kartica span, .kartica h1, .kartica h2, .kartica h3, .kartica h4, .kartica label, .kartica td, .kartica th, .kartica li, .kartica a { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
/* naslovi kartica i labele su goli <div> (boju im NE diramo, da namerno obojeni — crveni/akcenat — ostanu) — samo senka da se vide */
.kartica div { text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
/* modal nasleđuje glass izgled od kartica: providno staklo, beli tekst, lakše zatamnjenje da se slika nazire */
.premesti-modal h3, .premesti-modal .premesti-opcija, .premesti-modal .premesti-zatvori { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
.premesti-modal[open]::backdrop { background: rgba(0,0,0,0.45) !important; }
table, th, td { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
tr { background: rgba(0,0,0,0.2); }
tr:hover { background: rgba(0,0,0,0.35); }
thead th { background: rgba(0,0,0,0.4) !important; }
div:has(> canvas) { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 8px; }
/* elementi van kartica (i preskočeni tagovi) koje glass override gore ne dohvata —
dobijaju sopstvenu staklenu podlogu da se vide bez obzira na sliku ispod */
.nazad-link, .btn-sekundarno, .btn-obrisi-ghost, .cek-filter {
background: rgba(0,0,0,0.28) !important;
backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px);
border: 1px solid rgba(255,255,255,0.18) !important;
color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.8);
}
/* .nazad-link i checkbox-traka nemaju sopstveni padding — dajemo „pill" oblik */
.nazad-link, .cek-filter { padding: 6px 12px; border-radius: 8px; }
.nazad-link svg, .btn-sekundarno svg, .cek-filter svg { color: rgba(255,255,255,0.95) !important; stroke: rgba(255,255,255,0.95) !important; }
/* sitne napomene (npr. „maksimum 5 MB") i naslovi van kartica — svetli tekst + halo, bez podloge */
.pomocni-tekst { color: rgba(255,255,255,0.9) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.8); }
/* jača dvoslojna senka: oštar tamni obris + širi mekan halo, da naslov „iskoči" i na svetloj slici */
/* tri sloja u opadajućem zatamnjenju: skoro crn obris → upola → bled široki halo */
.naslov-traka { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.95), 0 0 8px rgba(0,0,0,0.5), 0 2px 16px rgba(0,0,0,0.25); }
/* info-banner u kartici: tekst je goli <div> koji glass override ne dohvata — ciljano mu damo svetli tekst + senku */
.info-banner div { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.55); }
/* Mobilni: backdrop-filter blur preko fiksne pozadinske slike često NE radi na
telefonima (iOS/Android), pa glass topbar propušta svetlu sliku i beli naslov se
gubi. Zato ne računamo na blur — dajemo čvrstu poluprovidnu podlogu i jaču senku. */
@media (max-width: 768px) {
.topbar { background: rgba(0,0,0,0.5) !important; }
.topbar-naslov { text-shadow: 0 1px 3px rgba(0,0,0,1), 0 0 8px rgba(0,0,0,0.85); }
.naslov-traka { text-shadow: 0 1px 3px rgba(0,0,0,1), 0 0 8px rgba(0,0,0,0.9), 0 2px 16px rgba(0,0,0,0.4); }
.pomocni-tekst { text-shadow: 0 1px 3px rgba(0,0,0,1), 0 0 6px rgba(0,0,0,0.85); }
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
var meni = document.getElementById('avatar-meni');
if (!meni) return;
meni.style.backdropFilter = 'none';
meni.style.webkitBackdropFilter = 'none';
meni.style.background = 'var(--kartica)';
});
</script>
{{end}}
</head>
<body{{if .LokalnaAnimacija}} data-animacija="{{.LokalnaAnimacija}}"{{end}}{{if .LokalniHover}} data-hover="{{.LokalniHover}}"{{end}}>
<div class="raspored">
<div class="sidebar-overlay" id="sidebar-overlay"></div>
{{template "sidebar" .}}
<div class="glavni-sadrzaj" id="glavni-sadrzaj">
{{template "topbar" .}}
<main class="sadrzaj" id="sadrzaj">
{{if .Flash}}
<div id="flash-toast" class="flash-toast flash-{{.Flash.Tip}}" role="alert">
<span class="flash-ikona">{{if eq .Flash.Tip "uspeh"}}✓{{else}}!{{end}}</span>
<span class="flash-tekst">{{.Flash.Poruka}}</span>
<button class="flash-zatvori" onclick="this.parentElement.remove()" aria-label="Zatvori">×</button>
</div>
<script>
(function() {
var t = document.getElementById('flash-toast');
if (!t) return;
setTimeout(function() {
t.classList.add('flash-izlaz');
setTimeout(function() { if (t.parentElement) t.remove(); }, 350);
}, 4000);
})();
</script>
{{end}}
{{block "sadrzaj" .}}{{end}}
</main>
</div>
</div>
<!-- alpine.js komponente (mora biti pre Alpine-a) -->
<script src="/static/js/ntech.js?v={{.AssetV}}" defer></script>
<!-- alpine.js CSP build (lokalno, bez unsafe-eval) -->
<script src="/static/js/alpine.csp.min.js" defer></script>
<!-- htmx za komunikaciju sa serverom (lokalno) -->
<script src="/static/js/htmx.min.js"></script>
<!-- sidebar logika — sidebar se NIKAD ne menja pri HTMX navigaciji, pa nema potrebe za swap logikom -->
<script>
(function() {
if (window._ntechSidebarDodato) return;
window._ntechSidebarDodato = true;
function mobilni() { return window.innerWidth <= 768; }
var sidebar = document.getElementById("sidebar");
var hamburger = document.getElementById("hamburger");
var overlay = document.getElementById("sidebar-overlay");
// skupljeno stanje pri učitavanju stranice
if (sidebar && !mobilni() && localStorage.getItem("sidebar-skupljen") === "true") {
sidebar.classList.add("skupljen");
}
document.documentElement.classList.remove('sidebar-init-skupljen');
if (typeof ntechInicijalizujPodmeni === 'function') ntechInicijalizujPodmeni();
// hamburger — sidebar element živi ceo vek stranice, listener se dodaje jednom
if (hamburger && sidebar && overlay) {
hamburger.addEventListener("click", function() {
if (mobilni()) {
sidebar.classList.toggle("otvoren");
overlay.classList.toggle("aktivan");
} else {
sidebar.classList.toggle("skupljen");
localStorage.setItem("sidebar-skupljen", sidebar.classList.contains("skupljen"));
}
});
overlay.addEventListener("click", function() {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
});
}
window.addEventListener("resize", function() {
if (!mobilni() && sidebar && overlay) {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
}
});
// aktivna stavka — ažurira se pri svakoj HTMX navigaciji
function azurirajAktivne() {
var putanja = window.location.pathname;
document.querySelectorAll('#sidebar .nav-stavka').forEach(function(el) {
el.classList.remove('aktivan');
});
document.querySelectorAll('#sidebar .nav-podmeni').forEach(function(el) {
el.classList.remove('otvoren');
var strelica = el.previousElementSibling && el.previousElementSibling.querySelector('.nav-strelica svg');
if (strelica) strelica.style.transform = 'rotate(0deg)';
});
document.querySelectorAll('#sidebar a.nav-stavka').forEach(function(el) {
var href = el.getAttribute('href');
if (!href) return;
if (putanja === href || putanja.startsWith(href + '/')) {
el.classList.add('aktivan');
var podmeni = el.closest('.nav-podmeni');
if (podmeni) {
podmeni.classList.add('otvoren');
var roditelj = podmeni.previousElementSibling;
if (roditelj) {
roditelj.classList.add('aktivan');
var str = roditelj.querySelector('.nav-strelica svg');
if (str) str.style.transform = 'rotate(180deg)';
}
}
}
});
}
document.addEventListener('htmx:afterSwap', azurirajAktivne);
})();
</script>
{{block "dodatni-js" .}}{{end}}
<!-- modal za potvrdu akcije -->
<div id="potvrda-modal" style="display:none;position:fixed;inset:0;z-index:9999;align-items:center;justify-content:center;">
<div id="potvrda-pozadina" style="position:absolute;inset:0;background:rgba(0,0,0,0.55);backdrop-filter:blur(3px);"></div>
<div style="position:relative;background:var(--kartica-pozadina);border:0.5px solid var(--ivica);border-radius:12px;padding:28px 28px 22px;max-width:380px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.35);">
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:20px;line-height:1.5;" id="potvrda-poruka"></div>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button id="potvrda-odustani" class="btn-sekundarno">Odustani</button>
<button id="potvrda-potvrdi" class="btn-opasno">Potvrdi</button>
</div>
</div>
</div>
<!-- CSRF i potvrda: inicijalizacija na učitavanju i posle htmx swap-a -->
<script>
(function() {
var modal = document.getElementById('potvrda-modal');
var poruka = document.getElementById('potvrda-poruka');
var btnPotvrdi = document.getElementById('potvrda-potvrdi');
var btnOdustani = document.getElementById('potvrda-odustani');
var pozadina = document.getElementById('potvrda-pozadina');
var _resolveFn = null;
function prikaziModal(tekst) {
return new Promise(function(resolve) {
poruka.textContent = tekst;
modal.style.display = 'flex';
_resolveFn = resolve;
});
}
function zatvoriModal(rezultat) {
modal.style.display = 'none';
if (_resolveFn) { _resolveFn(rezultat); _resolveFn = null; }
}
btnPotvrdi.addEventListener('click', function() { zatvoriModal(true); });
btnOdustani.addEventListener('click', function() { zatvoriModal(false); });
pozadina.addEventListener('click', function() { zatvoriModal(false); });
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.style.display === 'flex') zatvoriModal(false);
});
window._ntechPotvrdi = prikaziModal;
})();
function ntechInicijalizuj() {
var m = document.querySelector('meta[name="csrf-token"]');
if (m && m.content) {
document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) {
if (f.querySelector('input[name="_csrf"]')) return;
var i = document.createElement('input');
i.type = 'hidden'; i.name = '_csrf'; i.value = m.content;
f.appendChild(i);
});
}
document.querySelectorAll('[data-potvrda]').forEach(function(el) {
if (el._potvrda) return;
el._potvrda = true;
el.addEventListener('click', function(e) {
e.preventDefault();
window._ntechPotvrdi(el.getAttribute('data-potvrda')).then(function(ok) {
if (!ok) return;
// dugme unutar forme — submit forme
var forma = el.closest('form');
if (forma) { forma.submit(); return; }
// link
if (el.href) { window.location.href = el.href; }
});
});
});
}
if (!window._ntechCsrfDodato) {
window._ntechCsrfDodato = true;
document.addEventListener('htmx:afterSettle', ntechInicijalizuj);
}
document.addEventListener('DOMContentLoaded', ntechInicijalizuj);
</script>
</body>
</html>
{{end}}