⚡ Weristo
Cég admin panel — bejelentkezés
⚠️
Master admin belépés aktív — ezt a munkamenetet a master admin nyitotta meg.
🏠 Dashboard
🔑 API kulcs (saját rendszerintegrációhoz)
Ezt használd amikor a saját rendszered (n8n, Zapier, ChatGPT plugin, saját app) hív a Weristo API-jához. Csak te láthatod — bizalmas!
—
📋 Másolás
👥 Felhasználók
Csapattagok
Név Email Szerepkör Hozzáadva
Betöltés...
🤖 Chatbot beállítások
Widget beágyazás
Illeszd be ezt a kódot a weboldal </body> tagje elé:
—
📋 Másolás
🔗 Webhook konfiguráció
Eseménytípusok
📩 new_lead — Új lead rögzítésekor
💬 chat_message — Chatbot üzenet érkezésekor
📋 appointment_booked — Időpont foglalásakor
✅ lead_status_changed — Lead státusz változásakor
💳 Csomag & Számlázás
Jelenlegi csomag
Betöltés...
Csomagok
Pilot
Egyeztetés szerint
A builderrel készült weboldal örökre ingyenes. A pilot feltételeiről személyesen egyeztetünk.
Teljes rendszer
€10/ hó
Kb. havi nettó, a teljes rendszerre (CRM + AI-ügynökök).
⏳ Paddle fizetési integráció hamarosan elérhetővé válik. Csomag váltáshoz vedd fel velünk a kapcsolatot:
otto@weristo.de
💳 Stripe beállítás (Webshop fizetés)
💾 Stripe mentése
🌍 Szállítási országok
Szállítható országok (webshopban megjelenik)
Válaszd ki, mely országokba szállítasz. Ez megjelenik a webshopban az ajánlatkérő űrlapon.
💾 Mentés
📧 Értesítések
Értesítési e-mail-cím
Ide küldjük a rendszerértesítéseket, például a 15 napos CRM-emlékeztetőt azokról az ügyfelekről, akikkel régen volt kapcsolat. Ha üresen hagyod, a regisztrációs címedre megy: —
💾 Mentés
🏷️ Kedvezmények
Aktív kedvezmények
🔄 Frissít
➕ Új kedvezmény
Név
Típus
Termék
Feltétel
Kedvezmény
Érvényes
Aktív
Műveletek
Betöltés...
➕ Új kedvezmény
✕
Előnézet: Add meg a mezőket...
💾 Mentés
Mégse
🔩 Profil Kezelése
Szelvény profilok
🔄 Frissít
Kód
Leírás
Ár (€/rúd)
Hossz (mm)
Súly (kg)
Műveletek
Betöltés...
✏️ Profil szerkesztése
💾 Mentés
Mégse
➕ Új profil hozzáadása
+ Új szelvény profil
📦 Raktárkészlet
Készletek
⚠️ Riasztások
🔄 Frissít
Profil
Leírás
Hossz (mm)
Helyszín
Státusz
Betöltés...
📋 Ajánlatkérések (Webshop)
Beérkező ajánlatkérések
🔄 Frissít
Ügyfél
Email / Tel
Projekt
Hossz (m)
Státusz
Dátum
Betöltés...
💡 Ajánlatkérések a gelaender-hessen.de webshopból érkeznek. Minden új kérés automatikusan WA értesítést küld.
🌐 Weboldal szerkesztő
⚡
Mentés után azonnal él az oldal — látogatók azonnal látják az új tartalmat (cache automatikusan frissül).
— még nincs betöltve —
🔄 Előnézet frissítése
↗ Új lapon
Válassz egy szekciót a bal oldalon
A szerkesztő mezők itt jelennek meg.
👁 Előnézet frissítése
💾 Mentés és Élesítés
';
// Billing
document.getElementById('billing-plan-info').innerHTML =
'' +
'' + _esc((companyData.plan||'trial').toUpperCase()) + ' ' +
'Státusz: ' + (_esc(companyData.status||'active')) + ' ' +
'
';
}
async function doLogin() {
const email = document.getElementById('login-email').value.trim();
const pass = document.getElementById('login-pass').value;
if (!email || !pass) return;
document.getElementById('auth-err').style.display = 'none';
try {
const r = await weriFetch(API + '/login', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({email, password: pass})
});
const d = await r.json();
if (d.token || d.session_token || d.success) {
const token = await weriAuth.tokenFor(Object.assign({}, d, { token: d.token || d.session_token })); // [B13-FE] suti-modban a token nem kerul a localStorage-ba (csak jelzo), a csrf memoriaban
await initWithToken(token);
} else {
document.getElementById('auth-err').style.display = 'block';
}
} catch(e) {
document.getElementById('auth-err').style.display = 'block';
}
}
function doLogout() {
weriAuth.logout(['weri_ceo_token']); // [B13-FE] backend-logout (hatterben) + regi kulcs torlese
localStorage.removeItem('weri_ceo_token');
sessionToken = null;
companyData = null;
document.getElementById('auth-screen').style.display = 'flex';
document.getElementById('app-screen').style.display = 'none';
}
// --- Navigation ---
function showSec(id, el) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
var sec = document.getElementById('sec-' + id);
if (sec) sec.classList.add('active');
if (el) el.classList.add('active');
if (id === 'users') loadUsers();
if (id === 'chatbot') loadChatbotSettings();
if (id === 'discounts') loadDiscounts();
if (id === 'website') loadWebsiteEditor();
}
// --- Users ---
async function loadUsers() {
if (!companyId || !sessionToken) return;
const tbody = document.getElementById('users-tbody');
tbody.innerHTML = 'Betöltés... ';
try {
const r = await weriFetch(API + '/admin/company/' + companyId + '/users', {
headers: {'x-session-token': sessionToken}
});
const d = await r.json();
const users = d.users || [];
if (!users.length) {
tbody.innerHTML = 'Még nincs csapattag hozzáadva ';
return;
}
tbody.innerHTML = users.map(u =>
'' + (_esc(u.name||'—')) + ' ' + (_esc(u.email||'—')) + ' ' +
'' + (_esc(u.role||'member')) + ' ' +
'' + (u.created_at ? new Date(u.created_at).toLocaleDateString('hu-HU') : '—') + ' ' +
'Törlés '
).join('');
} catch(e) {
tbody.innerHTML = 'API hiba ';
}
}
async function addUser() {
const name = document.getElementById('new-user-name').value.trim();
const email = document.getElementById('new-user-email').value.trim();
if (!email) { showToast('Email kötelező!'); return; }
try {
const r = await weriFetch(API + '/admin/company/' + companyId + '/users', {
method: 'POST',
headers: {'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({name, email, role:'member'})
});
const d = await r.json();
if (d.ok) {
document.getElementById('new-user-name').value = '';
document.getElementById('new-user-email').value = '';
showToast('Felhasználó hozzáadva!');
loadUsers();
} else { showToast('Hiba: ' + (d.error||'ismeretlen')); }
} catch(e) { showToast('API hiba'); }
}
async function deleteUser(uid) {
if (!confirm('Biztosan törlöd?')) return;
try {
await weriFetch(API + '/admin/company/' + companyId + '/users/' + uid, {
method: 'DELETE',
headers: {'x-session-token':sessionToken}
});
showToast('Törölve');
loadUsers();
} catch(e) { showToast('API hiba'); }
}
// --- Chatbot ---
async function loadChatbotSettings() {
if (!companyId || !sessionToken) return;
try {
const r = await weriFetch(API + '/admin/companies/' + companyId, {
headers: {'x-session-token': sessionToken}
});
const d = await r.json();
const co = d.company || {};
document.getElementById('cb-name').value = co.chat_bot_name || 'Weri';
document.getElementById('cb-greeting').value = co.chat_welcome_message || 'Szia! Miben segíthetek?';
document.getElementById('cb-tone').value = co.chat_tone || 'professional';
document.getElementById('cb-color').value = co.chat_primary_color || '#00e5ff';
document.getElementById('cb-prompt').value = co.chat_master_prompt || '';
} catch(e) {}
}
async function saveChatbot() {
if (!companyId || !sessionToken) return;
try {
const r = await weriFetch(API + '/admin/companies/' + companyId, {
method: 'PUT',
headers: {'Content-Type':'application/json','x-session-token': sessionToken},
body: JSON.stringify({
chat_bot_name: document.getElementById('cb-name').value,
chat_welcome_message: document.getElementById('cb-greeting').value,
chat_tone: document.getElementById('cb-tone').value,
chat_primary_color: document.getElementById('cb-color').value,
chat_master_prompt: document.getElementById('cb-prompt').value
})
});
const d = await r.json();
if (d.success || d.ok) showToast('✅ Chatbot beállítások mentve!');
else showToast('Hiba: ' + (d.message||'ismeretlen'));
} catch(e) { showToast('API hiba'); }
}
// --- Webhook ---
async function saveWebhook() {
showToast('Webhook konfig mentve (hamarosan aktív)');
}
// --- Utils ---
function copyApiKey() {
const key = document.getElementById('api-key-box').textContent;
navigator.clipboard.writeText(key).then(() => showToast('API kulcs másolva!')).catch(() => {});
}
function copyWidgetSnippet() {
const snip = document.getElementById('widget-snippet').textContent;
navigator.clipboard.writeText(snip).then(() => showToast('Snippet másolva!')).catch(() => {});
}
function showToast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.style.display = 'block';
setTimeout(() => { t.style.display = 'none'; }, 2800);
}
// === LAMBDA INVENTORY & QUOTES ===
var lambdaProfiles = [];
async function loadInventory() {
if (!sessionToken) return;
try {
const r = await weriFetch(API + '/admin/step/inventory', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const tbody = document.getElementById('inv-body');
if (!tbody) return;
if (!d.success || !d.inventory || !d.inventory.length) {
tbody.innerHTML = 'Nincs készlet bejegyezve ';
return;
}
tbody.innerHTML = d.inventory.map(i => `
${_esc(i.profile_code||'—')}
${_esc(i.profile_desc||'—')}
${_esc(i.length_mm||'—')}
${_esc(i.location||'—')}
${_esc(i.status)}
`).join('');
} catch(e) { console.error('inv load error', e); }
}
async function loadProfiles() {
try {
const r = await weriFetch(API + '/lambda/products');
const d = await r.json();
if (d.ok && d.products) {
lambdaProfiles = d.products;
const sel = document.getElementById('inv-profile');
if (sel) {
sel.innerHTML = '— Profil választása — ' +
d.products.map(p => `${_esc(p.code)} — ${_esc(p.description||'')} `).join('');
}
}
} catch(e) {}
}
async function addInventoryItem() {
if (!sessionToken) return;
const profileId = document.getElementById('inv-profile').value;
const length = parseInt(document.getElementById('inv-length').value);
const location = document.getElementById('inv-location').value;
if (!profileId || !length) { alert('Profil és hossz kötelező!'); return; }
try {
const r = await weriFetch(API + '/admin/step/inventory', {
method: 'POST',
headers: {'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({profile_id: profileId, length_mm: length, quantity: 1, location})
});
const d = await r.json();
if (d.success) { showToast('✅ Tétel hozzáadva!'); loadInventory(); document.getElementById('inv-length').value = ''; }
else throw new Error(d.error || 'Hiba');
} catch(e) { showToast('❌ Hiba: ' + e.message); }
}
async function checkInventoryAlerts() {
if (!sessionToken) return;
try {
const r = await weriFetch(API + '/lambda/inventory/alerts', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const el = document.getElementById('inv-alerts');
if (d.ok && d.alerts && d.alerts.length) {
el.style.display = 'block';
el.innerHTML = '⚠️ Alacsony készlet: ' + d.alerts.map(a => `${_esc(a.profile_code)} (${_esc(a.current_qty)} db)`).join(', ') + ' — WA értesítés elküldve!';
} else {
el.style.display = 'block';
el.innerHTML = '✅ Minden készlet rendben!';
el.style.background = '#00e5ff11';
el.style.borderColor = '#00e5ff44';
el.style.color = '#00e5ff';
}
} catch(e) {}
}
async function loadQuotes() {
if (!sessionToken) return;
try {
const r = await weriFetch(API + '/lambda/quotes', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const tbody = document.getElementById('quotes-body');
if (!tbody) return;
if (!d.ok || !d.quotes || !d.quotes.length) {
tbody.innerHTML = 'Még nincs ajánlatkérés ';
return;
}
const STATUS_COLOR = {new:'#00e5ff',contacted:'#ffaa00',quoted:'#a78bff',won:'#00ff88',lost:'#ff4444'};
tbody.innerHTML = d.quotes.map(q => `
${_esc(q.customer_name||'—')}
${_esc(q.customer_email||'')}${_esc(q.customer_phone||'')}
${_esc(q.project_description||'—')}
${_esc(q.estimated_length_m||'—')}
${_esc(q.status)}
${q.created_at?new Date(q.created_at).toLocaleDateString('hu-HU'):'—'}
`).join('');
} catch(e) { console.error('quotes load error', e); }
}
async function updateQuoteStatus(id) {
const status = prompt('Státusz (new/contacted/quoted/won/lost):');
if (!status) return;
try {
const r = await weriFetch(API + '/lambda/quotes/' + id, {
method: 'PUT',
headers: {'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({status})
});
const d = await r.json();
if (d.ok) { showToast('✅ Státusz frissítve!'); loadQuotes(); }
} catch(e) {}
}
// === PROFILES ADMIN ===
async function loadProfilesAdmin() {
if (!sessionToken) return;
try {
const r = await weriFetch(API + '/admin/step/profiles', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const tbody = document.getElementById('profiles-body');
if (!tbody) return;
if (!d.profiles || !d.profiles.length) {
tbody.innerHTML = 'Nincs profil ';
return;
}
tbody.innerHTML = d.profiles.map(p => `
${p.image_url ? ` ` : ''}${_esc(p.code)}
${_esc(p.description||'—')}
${p.unit_price_eur ? _esc(p.unit_price_eur) + ' €' : '—'}
${_esc(p.unit_length_mm||6000)}
${_esc(p.unit_weight_kg||'—')}
✏️ Szerk.
🗑
`).join('');
} catch(e) { console.error('profiles load error', e); }
}
function editProfile(id, code, desc, price, length, weight, minstock) {
document.getElementById('edit-profile-id').value = id;
document.getElementById('edit-profile-code').value = code;
document.getElementById('edit-profile-desc').value = desc;
document.getElementById('edit-profile-price').value = price;
document.getElementById('edit-profile-length').value = length;
document.getElementById('edit-profile-weight').value = weight;
document.getElementById('edit-profile-minstock').value = minstock;
var imgUrl = arguments[7] || '';
document.getElementById('edit-profile-image').value = imgUrl;
document.getElementById('edit-profile-longdesc').value = arguments[8] || '';
var prev = document.getElementById('edit-profile-imgpreview');
var prevImg = document.getElementById('edit-profile-imgpreview-img');
if (imgUrl) { prevImg.src = _safeUrl(imgUrl); prev.style.display='block'; } else { prev.style.display='none'; }
document.getElementById('profile-edit-title').textContent = '✏️ Szerkesztés: ' + code;
document.getElementById('profile-edit-card').style.display = 'block';
document.getElementById('profile-edit-card').scrollIntoView({behavior:'smooth'});
}
function newProfile() {
document.getElementById('edit-profile-id').value = '';
document.getElementById('edit-profile-code').value = '';
document.getElementById('edit-profile-desc').value = '';
document.getElementById('edit-profile-price').value = '';
document.getElementById('edit-profile-length').value = '6000';
document.getElementById('edit-profile-weight').value = '';
document.getElementById('edit-profile-minstock').value = '5';
document.getElementById('profile-edit-title').textContent = '➕ Új profil';
document.getElementById('profile-edit-card').style.display = 'block';
document.getElementById('profile-edit-card').scrollIntoView({behavior:'smooth'});
}
function cancelProfileEdit() {
document.getElementById('profile-edit-card').style.display = 'none';
}
async function uploadProfileImage(input) {
const file = input.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
try {
const r = await weriFetch(API + '/admin/step/profiles/upload-image', {
method: 'POST',
headers: {'x-session-token': sessionToken},
body: formData
});
const d = await r.json();
if (d.url) {
document.getElementById('edit-profile-image').value = d.url;
document.getElementById('edit-profile-imgpreview-img').src = _safeUrl(d.url);
document.getElementById('edit-profile-imgpreview').style.display = 'block';
showToast('✅ Kép feltöltve!');
} else throw new Error(d.error || 'Upload hiba');
} catch(e) { showToast('❌ ' + e.message); }
}
async function saveProfile() {
if (!sessionToken) return;
const id = document.getElementById('edit-profile-id').value;
const payload = {
code: document.getElementById('edit-profile-code').value.trim(),
description: document.getElementById('edit-profile-desc').value.trim(),
unit_price_eur: parseFloat(document.getElementById('edit-profile-price').value)||null,
unit_length_mm: parseInt(document.getElementById('edit-profile-length').value)||6000,
unit_weight_kg: parseFloat(document.getElementById('edit-profile-weight').value)||null,
min_stock_qty: parseInt(document.getElementById('edit-profile-minstock').value)||5,
image_url: document.getElementById('edit-profile-image').value.trim()||null,
long_description: document.getElementById('edit-profile-longdesc').value.trim() || null
};
if (!payload.code) { alert('Kód kötelező!'); return; }
try {
const url = id ? API + '/admin/step/profiles/' + id : API + '/admin/step/profiles';
const method = id ? 'PUT' : 'POST';
const r = await weriFetch(url, {
method, headers: {'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify(payload)
});
const d = await r.json();
if (d.success || d.profile) {
showToast('✅ Profil mentve!');
cancelProfileEdit();
loadProfilesAdmin();
loadProfiles(); // refresh inventory dropdown too
} else throw new Error(d.error || 'Hiba');
} catch(e) { showToast('❌ Hiba: ' + e.message); }
}
async function deleteProfile(id, code, btn) {
if (!confirm('Törlöd a(z) ' + code + ' profilt?')) return;
if (!sessionToken) return;
try {
const r = await weriFetch(API + '/admin/step/profiles/' + id, {
method: 'DELETE', headers: {'x-session-token':sessionToken}
});
const d = await r.json();
if (d.success) { showToast('✅ Profil törölve!'); loadProfilesAdmin(); }
else throw new Error(d.error || 'Hiba');
} catch(e) { showToast('❌ ' + e.message); }
}
// Hook into showSec
var _origShowSec = window.showSec;
window.showSec = function(id, el) {
if (typeof _origShowSec === 'function') _origShowSec(id, el);
if (id === 'inventory') { loadInventory(); loadProfiles(); }
if (id === 'profiles') { loadProfilesAdmin(); }
if (id === 'quotes') { loadQuotes(); }
// discounts + website are already loaded by the original showSec (no double-load)
if (id === 'billing') { loadStripeSettings(); }
if (id === 'shipping') { loadShippingCountries(); }
if (id === 'notify') { loadNotifyEmail(); }
};
// ===== NOTIFY EMAIL [NOTIF-MAIL] 10-11 =====
async function loadNotifyEmail() {
try {
const r = await weriFetch(API + '/company/me', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const c = d.company || {};
document.getElementById('notify-email').value = c.notification_email || '';
document.getElementById('notify-reg-email').textContent = c.email || '—';
} catch(e) { console.error('loadNotifyEmail', e); }
}
async function saveNotifyEmail() {
const v = document.getElementById('notify-email').value.trim();
try {
const r = await weriFetch(API + '/company/settings', {
method:'PUT',
headers:{'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({notification_email: v})
});
const d = await r.json();
if (d.ok || d.success) showToast(v ? '✅ Értesítési cím mentve!' : '✅ Mentve — a regisztrációs címedre küldjük.');
else throw new Error(d.error || 'Hiba');
} catch(e) { showToast('❌ ' + e.message); }
}
// ===== STRIPE SETTINGS =====
async function loadStripeSettings() {
try {
const r = await weriFetch(API + '/company/stripe-settings', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
if (d.ok) {
document.getElementById('stripe-pk').value = d.stripe_pk || '';
document.getElementById('stripe-status').textContent = d.has_sk ? '✅ Secret Key mentve' : '⚠️ Secret Key még nincs beállítva';
}
} catch(e) {}
}
async function saveStripeSettings() {
const pk = document.getElementById('stripe-pk').value.trim();
const sk = document.getElementById('stripe-sk').value.trim();
if (!pk && !sk) { showToast('Add meg legalább a Publishable Key-t!'); return; }
try {
const r = await weriFetch(API + '/company/stripe-settings', {
method: 'POST',
headers: {'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({stripe_pk: pk, stripe_sk: sk})
});
const d = await r.json();
if (d.ok) { showToast('✅ Stripe beállítás mentve!'); loadStripeSettings(); document.getElementById('stripe-sk').value = ''; }
else throw new Error(d.error);
} catch(e) { showToast('❌ ' + e.message); }
}
// ===== DISCOUNTS =====
/* ==== UPGRADED DISCOUNT FUNCTIONS ==== */
const DISCOUNT_TYPE_LABEL = {
volume: 'Mennyiségi',
order_value: 'Összeg alapú',
date_range: 'Dátum alapú',
product: 'Termék alapú'
};
function discAuthHeaders(){
return { 'x-session-token': sessionToken };
}
function discFmtDate(s){ if (!s) return '—'; return s.slice(0,10); }
function discFmtCondition(d){
if (d.type === 'volume') return (d.min_qty || 1) + ' db-tól';
if (d.type === 'order_value') return parseFloat(d.min_order_eur||0).toFixed(2) + ' €-tól';
if (d.type === 'date_range') return discFmtDate(d.valid_from) + ' → ' + discFmtDate(d.valid_until);
if (d.type === 'product') return (d.min_qty ? d.min_qty + ' db-tól' : 'bármely mennyiség');
return '—';
}
function discFmtValidity(d){
if (d.valid_from && d.valid_until) return discFmtDate(d.valid_from) + ' – ' + discFmtDate(d.valid_until);
if (d.valid_until) return '—ig: ' + discFmtDate(d.valid_until);
if (d.valid_from) return discFmtDate(d.valid_from) + '—';
return '∞';
}
function discEsc(s){
return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
}
async function loadDiscounts(){
const tbody = document.getElementById('discounts-body');
if (!tbody) return;
tbody.innerHTML = 'Betöltés... ';
try {
const [dR, pR] = await Promise.all([
weriFetch(API + '/admin/step/discounts', { headers: discAuthHeaders() }),
weriFetch(API + '/admin/step/profiles', { headers: discAuthHeaders() })
]);
const dJ = await dR.json();
const pJ = await pR.json();
if (!dJ.ok) throw new Error(dJ.error || 'Hiba a kedvezmények betöltésekor');
const profiles = (pJ.profiles || pJ.data || []);
const sel = document.getElementById('disc-product');
if (sel) {
sel.innerHTML = '— Minden termék — '
+ profiles.map(p => '' + discEsc(p.code) + ' ').join('');
}
const codeById = {};
profiles.forEach(p => { codeById[p.id] = p.code; });
const list = dJ.discounts || [];
if (!list.length){
tbody.innerHTML = 'Nincs kedvezmény beállítva ';
return;
}
window._discountCache = list;
tbody.innerHTML = list.map(d => {
const typeLabel = DISCOUNT_TYPE_LABEL[d.type] || d.type;
const productName = d.product_id ? (_esc(codeById[d.product_id] || d.profile_code || '—')) : 'minden ';
const pct = d.discount_percent != null ? parseFloat(d.discount_percent).toFixed(1) + '%'
: d.discount_eur != null ? parseFloat(d.discount_eur).toFixed(2) + ' €' : '—';
return ''
+ '' + discEsc(d.name) + ' '
+ '' + discEsc(typeLabel) + ' '
+ '' + productName + ' '
+ '' + discEsc(discFmtCondition(d)) + ' '
+ '' + pct + ' '
+ '' + discEsc(discFmtValidity(d)) + ' '
+ ' '
+ ''
+ '✏️ '
+ '🗑 '
+ ' ';
}).join('');
} catch(e){
tbody.innerHTML = '❌ ' + discEsc(e.message) + ' ';
}
}
function openDiscountForm(){
document.getElementById('disc-id').value = '';
document.getElementById('discount-form-title').textContent = '➕ Új kedvezmény';
['disc-name','disc-percent','disc-minqty','disc-minorder','disc-validfrom','disc-validuntil','disc-product'].forEach(id => {
const el = document.getElementById(id); if (el) el.value = '';
});
document.getElementById('disc-type').value = 'volume';
updateDiscountFields();
document.getElementById('discount-add-card').style.display = 'block';
document.getElementById('disc-name').focus();
}
function closeDiscountForm(){
document.getElementById('discount-add-card').style.display = 'none';
}
function editDiscount(id){
const d = (window._discountCache || []).find(x => String(x.id) === String(id));
if (!d) return;
document.getElementById('disc-id').value = d.id;
document.getElementById('discount-form-title').textContent = '✏️ Kedvezmény szerkesztése';
document.getElementById('disc-name').value = d.name || '';
document.getElementById('disc-type').value = d.type || 'volume';
document.getElementById('disc-percent').value = d.discount_percent ?? '';
document.getElementById('disc-minqty').value = d.min_qty ?? '';
document.getElementById('disc-minorder').value = d.min_order_eur ?? '';
document.getElementById('disc-validfrom').value = d.valid_from ? d.valid_from.slice(0,10) : '';
document.getElementById('disc-validuntil').value = d.valid_until ? d.valid_until.slice(0,10) : '';
document.getElementById('disc-product').value = d.product_id || '';
updateDiscountFields();
document.getElementById('discount-add-card').style.display = 'block';
document.getElementById('discount-add-card').scrollIntoView({behavior:'smooth', block:'start'});
}
function updateDiscountFields(){
const type = document.getElementById('disc-type').value;
const map = {
'disc-field-minqty': (type === 'volume' || type === 'product'),
'disc-field-minorder': (type === 'order_value'),
'disc-field-validfrom': (type === 'date_range'),
'disc-field-validuntil': (type === 'date_range'),
'disc-field-product': (type === 'volume' || type === 'product'),
};
Object.entries(map).forEach(([id, show]) => {
const el = document.getElementById(id);
if (el) el.classList.toggle('hidden', !show);
});
}
async function saveDiscount(){
const id = document.getElementById('disc-id').value;
const payload = {
name: document.getElementById('disc-name').value.trim(),
type: document.getElementById('disc-type').value,
discount_percent: parseFloat(document.getElementById('disc-percent').value) || null,
min_qty: parseInt(document.getElementById('disc-minqty').value) || null,
min_order_eur: parseFloat(document.getElementById('disc-minorder').value) || null,
valid_from: document.getElementById('disc-validfrom').value || null,
valid_until: document.getElementById('disc-validuntil').value || null,
product_id: document.getElementById('disc-product').value || null,
active: true
};
if (payload.type !== 'volume' && payload.type !== 'product') payload.min_qty = null;
if (payload.type !== 'order_value') payload.min_order_eur = null;
if (payload.type !== 'date_range') { payload.valid_from = null; payload.valid_until = null; }
if (payload.type !== 'volume' && payload.type !== 'product') payload.product_id = null;
if (!payload.name) { showToast('⚠️ A név kötelező.'); return; }
if (!payload.discount_percent || payload.discount_percent <= 0) { showToast('⚠️ A kedvezmény % kötelező.'); return; }
try {
const url = id ? (API + '/admin/step/discounts/' + encodeURIComponent(id)) : (API + '/admin/step/discounts');
const r = await weriFetch(url, {
method: id ? 'PUT' : 'POST',
headers: { 'Content-Type':'application/json', ...discAuthHeaders() },
body: JSON.stringify(payload)
});
const d = await r.json();
if (!d.ok && !d.success) throw new Error(d.error || 'Mentési hiba');
showToast(id ? '✅ Kedvezmény frissítve' : '✅ Kedvezmény mentve');
closeDiscountForm();
loadDiscounts();
} catch(e){ showToast('❌ ' + e.message); }
}
async function toggleDiscount(id, active){
try {
const r = await weriFetch(API + '/admin/step/discounts/' + encodeURIComponent(id), {
method: 'PATCH',
headers: { 'Content-Type':'application/json', ...discAuthHeaders() },
body: JSON.stringify({ active })
});
const d = await r.json().catch(() => ({}));
if (r.ok || d.ok) showToast(active ? '✅ Aktiválva' : '⏸️ Szüneteltetve');
else throw new Error(d.error || 'Hiba');
} catch(e){ showToast('❌ ' + e.message); loadDiscounts(); }
}
async function deleteDiscount(id){
if (!confirm('Biztosan törlöd ezt a kedvezményt?')) return;
try {
const r = await weriFetch(API + '/admin/step/discounts/' + encodeURIComponent(id), {
method: 'DELETE', headers: discAuthHeaders()
});
const d = await r.json().catch(() => ({}));
if (!r.ok && !d.ok) throw new Error(d.error || 'Törlési hiba');
showToast('🗑 Kedvezmény törölve');
loadDiscounts();
} catch(e){ showToast('❌ ' + e.message); }
}
// ===== SHIPPING COUNTRIES =====
const ALL_COUNTRIES = [
{code:'DE',name:'Németország'},
{code:'AT',name:'Ausztria'},
{code:'CH',name:'Svájc'},
{code:'HU',name:'Magyarország'},
{code:'FR',name:'Franciaország'},
{code:'IT',name:'Olaszország'},
{code:'NL',name:'Hollandia'},
{code:'BE',name:'Belgium'},
{code:'PL',name:'Lengyelország'},
{code:'CZ',name:'Csehország'},
{code:'SK',name:'Szlovákia'},
{code:'RO',name:'Románia'},
{code:'ES',name:'Spanyolország'},
{code:'SE',name:'Svédország'},
{code:'DK',name:'Dánia'},
{code:'FI',name:'Finnország'},
{code:'NO',name:'Norvégia'},
{code:'PT',name:'Portugália'},
{code:'LU',name:'Luxemburg'},
{code:'SI',name:'Szlovénia'},
{code:'HR',name:'Horvátország'}
];
async function loadShippingCountries() {
try {
const r = await weriFetch(API + '/company/me', {headers:{'x-session-token':sessionToken}});
const d = await r.json();
const current = d.company?.shipping_countries || d.shipping_countries || ['DE','AT','CH'];
const container = document.getElementById('shipping-countries-checkboxes');
container.innerHTML = ALL_COUNTRIES.map(c => {
const checked = current.includes(c.code) ? 'checked' : '';
return ' ' + _esc(c.name) + ' (' + _esc(c.code) + ') ';
}).join('');
} catch(e) { console.error('loadShippingCountries', e); }
}
async function saveShippingCountries() {
const checked = Array.from(document.querySelectorAll('#shipping-countries-checkboxes input:checked')).map(el => el.value);
if (!checked.length) { showToast('Legalább egy országot válassz!'); return; }
try {
const r = await weriFetch(API + '/company/settings', {
method:'PUT',
headers:{'Content-Type':'application/json','x-session-token':sessionToken},
body: JSON.stringify({shipping_countries: checked})
});
const d = await r.json();
if (d.ok || d.success) showToast('✅ Szállítási országok mentve!');
else throw new Error(d.error || 'Hiba');
} catch(e) { showToast('❌ ' + e.message); }
}
/* === Website Editor ============================================================= */
(function(){
let weSiteId = null, weSiteUrl = null, weSections = [], weActiveKey = null, weDirty = false;
const WE_DEFAULT_SECTIONS = [
{ key:'hero', label:'Hero', icon:'🎯', fields:[
{ key:'title', label:'Főcím', type:'text', value:'' },
{ key:'subtitle', label:'Alcím / leírás', type:'textarea', value:'' },
{ key:'cta_primary', label:'Elsődleges gomb szövege', type:'text', value:'' },
{ key:'cta_primary_link', label:'Elsődleges gomb link', type:'text', value:'#angebot' },
{ key:'cta_secondary', label:'Másodlagos gomb szövege', type:'text', value:'' },
{ key:'background_image', label:'Háttérkép URL', type:'image', value:'' },
]},
{ key:'stats', label:'Számok / Stats', icon:'📊', fields:[
{ key:'stat1_num', label:'1. szám', type:'text', value:'' },
{ key:'stat1_lbl', label:'1. címke', type:'text', value:'' },
{ key:'stat2_num', label:'2. szám', type:'text', value:'' },
{ key:'stat2_lbl', label:'2. címke', type:'text', value:'' },
{ key:'stat3_num', label:'3. szám', type:'text', value:'' },
{ key:'stat3_lbl', label:'3. címke', type:'text', value:'' },
]},
{ key:'services', label:'Szolgáltatások / Termékek', icon:'🛠️', fields:[
{ key:'title', label:'Szekció címe', type:'text', value:'' },
{ key:'subtitle', label:'Szekció alcíme', type:'textarea', value:'' },
]},
{ key:'about', label:'Rólunk', icon:'ℹ️', fields:[
{ key:'title', label:'Cím', type:'text', value:'' },
{ key:'text', label:'Szöveg', type:'textarea', value:'' },
{ key:'image', label:'Kép URL', type:'image', value:'' },
]},
{ key:'contact', label:'Kapcsolat / Footer', icon:'📞', fields:[
{ key:'phone', label:'Telefon', type:'text', value:'' },
{ key:'email', label:'Email', type:'text', value:'' },
{ key:'address', label:'Cím', type:'text', value:'' },
{ key:'footer_text', label:'Footer alsó sor', type:'text', value:'' },
]},
];
window.loadWebsiteEditor = async function(){
if (!companyId || !sessionToken){ weRenderEmpty('Bejelentkezés szükséges.'); return; }
weRenderLoading();
try {
const r = await weriFetch(API + '/builder/sites?company_id=' + encodeURIComponent(companyId), {
headers: {'x-session-token': sessionToken}
});
const d = await r.json();
const site = (d.sites && d.sites[0]) || d.site || null;
if (!site){ weRenderEmpty('Ehhez a céghez még nincs generált weboldal.'); return; }
weSiteId = site.id || site.site_id;
weSiteUrl = site.url || site.public_url || ('https://weristo.de/sites/' + (site.slug || companyId) + '/');
let secs = [];
try {
const rs = await weriFetch(API + '/builder/sites/' + weSiteId + '/sections', {
headers: {'x-session-token': sessionToken}
});
const ds = await rs.json();
secs = ds.sections || [];
} catch(e) {}
weSections = secs.length ? secs.map(weNormalizeSection) : JSON.parse(JSON.stringify(WE_DEFAULT_SECTIONS));
weRenderSectionList();
weLoadPreview();
weSelectSection(weSections[0].key);
} catch(e){ weRenderEmpty('Hiba: ' + e.message); }
};
function weNormalizeSection(raw){
const def = WE_DEFAULT_SECTIONS.find(s => s.key === raw.key) || null;
const fields = (raw.fields && raw.fields.length)
? raw.fields
: (def ? def.fields.map(f => ({...f, value: (raw.content && raw.content[f.key]) || ''})) : []);
return { id: raw.id || null, key: raw.key, label: raw.label || (def && def.label) || raw.key, icon: raw.icon || (def && def.icon) || '📄', fields };
}
function weRenderLoading(){
document.getElementById('we-sections').innerHTML = 'Betöltés...
';
document.getElementById('we-editor-title').textContent = 'Betöltés...';
document.getElementById('we-editor-fields').innerHTML = 'Adatok betöltése...
';
document.getElementById('we-save-row').style.display = 'none';
}
function weRenderEmpty(msg){
document.getElementById('we-sections').innerHTML = '' + _esc(msg) + '
';
document.getElementById('we-editor-title').textContent = 'Nem elérhető';
document.getElementById('we-editor-fields').innerHTML = '' + _esc(msg) + '
';
document.getElementById('we-save-row').style.display = 'none';
document.getElementById('we-current-url').textContent = '— nincs aktív weboldal —';
}
function weRenderSectionList(){
const wrap = document.getElementById('we-sections');
wrap.innerHTML = weSections.map(s =>
'' +
'' + _esc(s.icon) + ' ' + _esc(s.label) + '
'
).join('');
}
window.weSelectSection = function(key){
if (weDirty && !confirm('Mentetlen változások vannak. Eldobod?')) return;
weDirty = false; weActiveKey = key; weRenderSectionList();
const sec = weSections.find(s => s.key === key);
if (!sec) return;
document.getElementById('we-editor-title').textContent = sec.icon + ' ' + sec.label + ' — szerkesztés';
const fieldsHtml = sec.fields.map(f => {
const fid = 'we-f-' + sec.key + '-' + f.key;
const safeVal = (_esc(f.value || '')).replace(/"/g,'"').replace(/' + _esc(f.label) + ' ';
if (f.type === 'image') return '';
return '' + _esc(f.label) + '
';
}).join('');
document.getElementById('we-editor-fields').innerHTML = '' + (fieldsHtml || '
Nincs szerkeszthető mező.
') + '
';
document.getElementById('we-save-row').style.display = 'flex';
document.getElementById('we-status').textContent = '';
};
window.weMarkDirty = function(secKey, fieldKey, val){
const sec = weSections.find(s => s.key === secKey);
if (!sec) return;
const f = sec.fields.find(x => x.key === fieldKey);
if (!f) return;
f.value = val; weDirty = true;
document.getElementById('we-status').textContent = 'Mentetlen változások...';
};
function weLoadPreview(){
if (!weSiteUrl) return;
document.getElementById('we-current-url').textContent = weSiteUrl;
weReloadPreview();
}
window.weReloadPreview = function(){
if (!weSiteUrl){ showToast('Nincs betöltött weboldal.'); return; }
const sep = weSiteUrl.includes('?') ? '&' : '?';
const iframe = document.getElementById('we-preview');
const loading = document.getElementById('we-iframe-loading');
if (loading) loading.style.display = 'flex';
iframe.onload = function(){ if (loading) loading.style.display = 'none'; };
iframe.src = _safeUrl(weSiteUrl + sep + '_t=' + Date.now());
};
window.weOpenInNewTab = function(){
if (!weSiteUrl){ showToast('Nincs betöltött weboldal.'); return; }
window.open(_safeUrl(weSiteUrl + '?_t=' + Date.now()), '_blank');
};
window.weSaveAndPublish = async function(){
if (!weSiteId){ showToast('Nincs aktív weboldal.'); return; }
const statusEl = document.getElementById('we-status');
if (statusEl) statusEl.textContent = 'Mentés...';
const payload = { sections: weSections.map(s => ({
id: s.id || null, key: s.key, label: s.label,
content: s.fields.reduce((acc, f) => { acc[f.key] = f.value || ''; return acc; }, {})
}))};
try {
const r = await weriFetch(API + '/builder/sites/' + weSiteId + '/sections', {
method: 'POST',
headers: { 'Content-Type':'application/json', 'x-session-token': sessionToken },
body: JSON.stringify(payload)
});
const d = await r.json();
if (!(d.ok || d.success)) throw new Error(d.error || d.message || 'Ismeretlen hiba');
weDirty = false;
if (statusEl) { statusEl.textContent = '✅ Mentve — ' + new Date().toLocaleTimeString('hu-HU'); statusEl.className = 'we-status ok'; }
showToast('✅ Weboldal frissítve!');
weReloadPreview();
} catch(e){
if (statusEl) { statusEl.textContent = '❌ Hiba: ' + e.message; statusEl.className = 'we-status err'; }
showToast('❌ Mentés sikertelen: ' + e.message);
}
};
})();