دنیای فیلتر کنکاش
ورود با شماره موبایل
👤
—
‹
💳 مانده حساب مشتری
🔍
در حال بارگذاری...
‹
جزئیات مشتری
‹
⏳ فاکتورهای تسویه‌نشده
🔍
در حال بارگذاری...
‹
🔎 جستجوی فاکتور
‹
💰 قیمت محصولات
🔍
‹
📊 گزارش فروش
در حال محاسبه...
‹
📦 کاتالوگ کالاها
🔍
‹
جزئیات کالا
‹
⚙️ تنظیمات
در حال بارگذاری...
function showScreen(id) { document.querySelectorAll('.screen').forEach(s => { s.classList.remove('active'); s.style.display = 'none'; }); const el = document.getElementById('screen-' + id); el.classList.add('active'); el.style.display = (id === 'otp') ? 'flex' : 'block'; const header = document.getElementById('app-header'); header.style.display = (id === 'login' || id === 'otp') ? 'none' : 'flex'; state.currentScreen = id; } function navigate(id) { state.screenHistory.push(state.currentScreen); showScreen(id); } function goBack() { const prev = state.screenHistory.pop(); if (prev) showScreen(prev); else showScreen('menu'); } // ══════════════════════════════════════ // Toast // ══════════════════════════════════════ function showToast(msg, dur = 2500) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), dur); } // ══════════════════════════════════════ // Number Formatting // ══════════════════════════════════════ function fmtNum(n) { n = Number(n) || 0; return n.toLocaleString('fa-IR'); } function fmtRial(n) { n = Number(n) || 0; if (Math.abs(n) >= 1e9) return (n/1e9).toFixed(1).toLocaleString('fa-IR') + ' م.ت'; if (Math.abs(n) >= 1e6) return (n/1e6).toFixed(0).toLocaleString('fa-IR') + ' ه.ت'; return fmtNum(n) + ' ر'; } // ══════════════════════════════════════ // Auth // ══════════════════════════════════════ async function doLogin() { const phone = document.getElementById('login-phone').value.trim(); const password = (document.getElementById('login-password')?.value || '').trim(); const errEl = document.getElementById('login-error'); errEl.textContent = ''; if (!phone.match(/^09\d{9}$/)) { errEl.textContent = '⚠️ شماره موبایل معتبر وارد کنید (09xxxxxxxxx)'; return; } if (!password) { errEl.textContent = '⚠️ رمز عبور را وارد کنید'; return; } const btn = document.getElementById('login-btn'); btn.disabled = true; btn.textContent = 'در حال بررسی...'; try { if (DEMO_MODE) { await delay(800); const demoUsers = { '09144809405': { username: 'مدیر', display: 'مدیر سیستم', regions: [], isAdmin: true }, '09015809405': { username: 'مدیر', display: 'مدیر سیستم', regions: [], isAdmin: true }, '09140000001': { username: 'siamand', display: 'سیامند نادریان', regions: ['bokan siamak','mahabad siamak','omde siamak'], isAdmin: false }, '09140000002': { username: 'anvar', display: 'انور قریشی', regions: ['divandare','saghez siamak'], isAdmin: false }, '09140000003': { username: 'morteza', display: 'مرتضی سلامت', regions: ['shahin dezh'], isAdmin: false }, }; const user = demoUsers[phone]; if (!user) { errEl.textContent = '❌ شماره موبایل شناسایی نشد'; return; } state.username = user.username; state.displayName = user.display; state.regions = user.regions; state.isAdmin = user.isAdmin; afterLogin(); return; } const resp = await fetch(API_BASE + '/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json', 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ phone, password, tg_user_id: tg?.initDataUnsafe?.user?.id }), }); const data = await resp.json(); if (!data.ok) { errEl.textContent = '❌ ' + (data.error || 'خطا در ورود'); return; } state.username = data.username; state.token = data.token || ''; state.displayName = data.display_name || data.username; state.regions = data.regions || []; state.isAdmin = data.is_admin || false; afterLogin(); } catch(e) { errEl.textContent = '❌ خطای شبکه: ' + (e.message || e.toString()); } finally { btn.disabled = false; btn.textContent = 'ورود'; } } function afterLogin() { // ذخیره در sessionStorage برای ورود خودکار try { sessionStorage.setItem('kankash_token', state.token); sessionStorage.setItem('kankash_username', state.username); sessionStorage.setItem('kankash_display', state.displayName); sessionStorage.setItem('kankash_regions', JSON.stringify(state.regions)); sessionStorage.setItem('kankash_isAdmin', state.isAdmin ? '1' : '0'); sessionStorage.setItem('kankash_userType', state.userType || ''); sessionStorage.setItem('kankash_priceTier', String(state.priceTier || 1)); } catch(e) {} document.getElementById('screen-login').classList.add('hidden-login'); document.getElementById('welcome-name').textContent = state.displayName; document.getElementById('welcome-region').textContent = state.isAdmin ? '👑 مدیر سیستم' : ('📍 ' + state.regions.slice(0,2).join(' | ')); document.getElementById('header-sub').textContent = state.isAdmin ? '👑 ' + state.displayName : '👤 ' + state.displayName; buildMainMenu(); showScreen('menu'); loadBackgroundData(); } function logout() { if (!confirm('آیا از سیستم خارج می‌شوید؟')) return; try { sessionStorage.removeItem('kankash_token'); } catch(e) {} state = { username:null, token:null, displayName:null, regions:[], isAdmin:false, customers:[], unsettledData:[], prices:[], priceGroups:{}, currentScreen:'login', screenHistory:[] }; showScreen('login'); document.getElementById('login-error').textContent = ''; } // ══════════════════════════════════════ // Main Menu Builder // ══════════════════════════════════════ const MAIN_MENUS = [ { key:'balance', icon:'💳', label:'مانده حساب', desc:'تسویه مشتریان', screen:'balance', always:true, forCustomer:true }, { key:'unsettled', icon:'⏳', label:'فاکتور تسویه‌نشده', desc:'بدهی‌های باز', screen:'unsettled', always:true }, { key:'invoice', icon:'🔎', label:'جستجوی فاکتور', desc:'شماره فاکتور', screen:'invoice', always:true }, { key:'price', icon:'💰', label:'قیمت محصولات', desc:'لیست و موجودی', screen:'price', always:true, forCustomer:true }, { key:'sales', icon:'📊', label:'گزارش فروش', desc:'پورسانت و عملکرد', screen:'sales', admin:true }, { key:'catalog', icon:'📦', label:'کاتالوگ کالاها', desc:'مشخصات و قیمت', screen:'catalog', always:true, forCustomer:true }, { key:'settings', icon:'⚙️', label:'تنظیمات', desc:'مدیریت بات و کاربران', screen:'settings', admin:true }, ]; function buildMainMenu() { const grid = document.getElementById('main-menu-grid'); grid.innerHTML = ''; const isCustomer = state.userType === 'customer'; MAIN_MENUS.forEach(m => { if (m.admin && !state.isAdmin) return; if (!m.always && !state.isAdmin) return; if (isCustomer && !m.forCustomer) return; const d = document.createElement('div'); d.className = 'menu-card'; d.innerHTML = `
${m.icon}
${m.label}
${m.desc}
`; d.onclick = () => openMenu(m); grid.appendChild(d); }); } function openMenu(m) { navigate(m.screen); if (m.screen === 'balance') renderBalanceScreen(); if (m.screen === 'unsettled') renderUnsettledScreen(); if (m.screen === 'price') renderPriceScreen(); if (m.screen === 'sales') renderSalesScreen('today'); if (m.screen === 'catalog') renderCatalogScreen(); if (m.screen === 'settings') renderSettingsScreen(); } // ══════════════════════════════════════ // Background Data Loader (Demo) // ══════════════════════════════════════ async function loadBackgroundData() { if (DEMO_MODE) { loadDemoData(); return; } try { const resp = await fetch(API_BASE + '/api/data', { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }); const data = await resp.json(); state.customers = data.customers || []; state.unsettledData = data.unsettled || []; state.prices = data.prices || []; state.priceGroups = data.price_groups || {}; } catch(e) { showToast('⚠️ خطا در بارگذاری اطلاعات'); } } function loadDemoData() { // Demo customers state.customers = [ { code:'C001', name:'محمد احمدی', region:'bokan siamak', balance:85000000, type:'بد' }, { code:'C002', name:'علی رضایی', region:'mahabad siamak', balance:-12000000, type:'بس' }, { code:'C003', name:'حسین محمدی', region:'divandare', balance:230000000, type:'بد' }, { code:'C004', name:'رضا کریمی', region:'saghez siamak', balance:0, type:'بد' }, { code:'C005', name:'فرهاد صادقی', region:'omde siamak', balance:56000000, type:'بد' }, { code:'C006', name:'کمال نوروزی', region:'shahin dezh', balance:-8000000, type:'بس' }, { code:'C007', name:'سیروس عباسی', region:'bokan siamak', balance:145000000, type:'بد' }, { code:'C008', name:'مجید حسینی', region:'mahabad siamak', balance:67000000, type:'بد' }, ]; // Demo unsettled state.unsettledData = [ { code:'F10023', cust:'محمد احمدی', date:'۱۴۰۴/۰۳/۱۵', amount:45000000, days:32, region:'bokan siamak' }, { code:'F10031', cust:'حسین محمدی', date:'۱۴۰۴/۰۲/۲۸', amount:138000000, days:48, region:'divandare' }, { code:'F10045', cust:'سیروس عباسی', date:'۱۴۰۴/۰۳/۰۵', amount:88000000, days:42, region:'bokan siamak' }, { code:'F10052', cust:'مجید حسینی', date:'۱۴۰۴/۰۴/۰۱', amount:22000000, days:15, region:'mahabad siamak' }, { code:'F10067', cust:'فرهاد صادقی', date:'۱۴۰۴/۰۱/۲۰', amount:56000000, days:90, region:'omde siamak' }, { code:'F10071', cust:'رضا کریمی', date:'۱۴۰۴/۰۳/۲۵', amount:18000000, days:22, region:'saghez siamak' }, ]; // Demo prices state.prices = [ { code:'L001', name:'روغن موتور کاسترول 5W30', group:'روغن موتور', price:980000, stock:48 }, { code:'L002', name:'روغن موتور توتال 10W40', group:'روغن موتور', price:850000, stock:32 }, { code:'L003', name:'روغن موتور موبیل 15W50', group:'روغن موتور', price:1120000, stock:15 }, { code:'F001', name:'فیلتر هوا پراید', group:'فیلتر', price:180000, stock:120 }, { code:'F002', name:'فیلتر روغن پراید', group:'فیلتر', price:95000, stock:200 }, { code:'F003', name:'فیلتر هوا پژو 206', group:'فیلتر', price:220000, stock:85 }, { code:'F004', name:'فیلتر روغن پژو 405', group:'فیلتر', price:130000, stock:145 }, { code:'G001', name:'گیربکس کامل پراید', group:'گیربکس', price:12500000, stock:4 }, { code:'G002', name:'روغن گیربکس 80W90', group:'گیربکس', price:450000, stock:22 }, { code:'P001', name:'لوله گاز فشار قوی', group:'لوله', price:380000, stock:60 }, { code:'P002', name:'اتصال سه‌راهی ½"', group:'اتصالات', price:125000, stock:200 }, ]; const g = {}; state.prices.forEach(p => { if (!g[p.group]) g[p.group] = []; g[p.group].push(p); }); state.priceGroups = g; } // ══════════════════════════════════════ // Balance Screen // ══════════════════════════════════════ let _filteredCustomers = []; function renderBalanceScreen() { _filteredCustomers = filterByRegion(state.customers); document.getElementById('balance-search').value = ''; renderCustomerList(_filteredCustomers); } function filterByRegion(list) { if (state.isAdmin || !state.regions.length) return list; const norm = new Set(state.regions.map(r => r.trim().toLowerCase())); return list.filter(c => norm.has((c.region||'').trim().toLowerCase())); } function renderCustomerList(list) { const el = document.getElementById('balance-list'); if (!list.length) { el.innerHTML = `
📭
مشتری‌ای یافت نشد
`; return; } el.innerHTML = list.map(c => { const pos = c.balance > 0; const cls = c.balance > 0 ? 'amount-pos' : c.balance < 0 ? 'amount-neg' : ''; const lbl = c.balance > 0 ? 'بدهکار' : c.balance < 0 ? 'بستانکار' : 'تسویه'; return `
${c.name}
${fmtRial(Math.abs(c.balance))}
📍 ${c.region||'—'}
${lbl}
`; }).join(''); } function filterCustomers() { const q = document.getElementById('balance-search').value.trim().toLowerCase(); if (!q) { renderCustomerList(_filteredCustomers); return; } renderCustomerList(_filteredCustomers.filter(c => c.name.toLowerCase().includes(q) || (c.region||'').toLowerCase().includes(q) )); } function openCustomer(code) { const cust = state.customers.find(c => c.code === code); if (!cust) return; navigate('cust-detail'); document.getElementById('cust-detail-title').textContent = cust.name; if (DEMO_MODE) { // Demo ledger const ledger = [ { date:'۱۴۰۴/۰۱/۱۰', desc:'فاکتور فروش F9821', bed:45000000, bes:0 }, { date:'۱۴۰۴/۰۲/۰۵', desc:'دریافت نقدی', bed:0, bes:30000000 }, { date:'۱۴۰۴/۰۳/۱۸', desc:'فاکتور فروش F10023', bed:40000000, bes:0 }, { date:'۱۴۰۴/۰۴/۰۱', desc:'چک شماره 450123', bed:0, bes:15000000 }, ]; const invs = [ { code:'F9821', date:'۱۴۰۴/۰۱/۱۰', amount:45000000, settled:'بله' }, { code:'F10023', date:'۱۴۰۴/۰۳/۱۸', amount:40000000, settled:'خیر' }, ]; renderCustomerDetail(cust, ledger, invs); return; } fetch(API_BASE + '/api/customer/' + code, { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }).then(r => r.json()).then(data => { renderCustomerDetail(cust, data.ledger || [], data.invoices || []); }).catch(() => showToast('خطا در بارگذاری')); } function renderCustomerDetail(cust, ledger, invoices) { const balCls = cust.balance > 0 ? 'amount-pos' : cust.balance < 0 ? 'amount-neg' : ''; const balLbl = cust.balance > 0 ? '🔴 بدهکار' : cust.balance < 0 ? '🟢 بستانکار' : '✅ تسویه'; let html = `
📋 اطلاعات کلی
نام مشتری${cust.name}
منطقه${cust.region||'—'}
وضعیت${balLbl}
مانده${fmtNum(Math.abs(cust.balance))} ریال
📑 آخرین فاکتورها
${invoices.length ? invoices.map(inv => `
📄 ${inv.code}
${fmtRial(inv.amount)}
${inv.date} | تسویه: ${inv.settled}
`).join('') : '
فاکتوری ثبت نشده
'}
📒 دفتر حساب
${ledger.length ? ledger.map(l => `
${l.date}
${l.desc}
${l.bed>0?'+'+fmtRial(l.bed):'-'+fmtRial(l.bes)}
`).join('') : '
رکوردی یافت نشد
'}
`; document.getElementById('cust-detail-content').innerHTML = html; } // ══════════════════════════════════════ // Unsettled Screen // ══════════════════════════════════════ let _filteredUnsettled = []; function renderUnsettledScreen() { _filteredUnsettled = filterByRegion(state.unsettledData.map(u => ({...u, region: u.region||''}))); document.getElementById('unsettled-search').value = ''; renderUnsettledList(_filteredUnsettled); } function renderUnsettledList(list) { const el = document.getElementById('unsettled-list'); if (!list.length) { el.innerHTML = `
✅
فاکتور تسویه‌نشده‌ای وجود ندارد
`; return; } // sort by days desc list = [...list].sort((a,b) => (b.days||0)-(a.days||0)); const total = list.reduce((s,u) => s + (u.amount||0), 0); el.innerHTML = `
تعداد فاکتور
${fmtNum(list.length)}
جمع مانده
${fmtRial(total)}
${list.map(u => { const urgentCls = u.days > 60 ? 'badge-red' : u.days > 30 ? 'badge-yellow' : 'badge-blue'; return `
📄 ${u.code}
${fmtRial(u.amount)}
${u.cust} | ${u.date}
${fmtNum(u.days)} روز
`; }).join('')}`; } function filterUnsettled() { const q = document.getElementById('unsettled-search').value.trim().toLowerCase(); if (!q) { renderUnsettledList(_filteredUnsettled); return; } renderUnsettledList(_filteredUnsettled.filter(u => u.cust.toLowerCase().includes(q) || u.code.toLowerCase().includes(q) )); } // ══════════════════════════════════════ // Invoice Search // ══════════════════════════════════════ function searchInvoiceDirect(code) { navigate('invoice'); document.getElementById('invoice-num').value = code; searchInvoice(); } function searchInvoice() { const code = document.getElementById('invoice-num').value.trim(); const resEl = document.getElementById('invoice-result'); if (!code) { resEl.innerHTML = ''; return; } resEl.innerHTML = '
'; if (DEMO_MODE) { setTimeout(() => { const demoInvs = { 'F10023': { code:'F10023', cust:'محمد احمدی', date:'۱۴۰۴/۰۳/۱۵', amount:45000000, settled:false, items:[ { name:'روغن موتور کاسترول 5W30', qty:10, price:980000, total:9800000 }, { name:'فیلتر هوا پراید', qty:20, price:180000, total:3600000 }, { name:'روغن موتور توتال 10W40', qty:20, price:850000, total:17000000 }, ]}, 'F10031': { code:'F10031', cust:'حسین محمدی', date:'۱۴۰۴/۰۲/۲۸', amount:138000000, settled:false, items:[ { name:'گیربکس کامل پراید', qty:3, price:12500000, total:37500000 }, { name:'روغن گیربکس 80W90', qty:10, price:450000, total:4500000 }, ]}, }; const inv = demoInvs[code]; if (!inv) { resEl.innerHTML = '
🔍
فاکتور یافت نشد
'; return; } renderInvoice(inv, resEl); }, 600); return; } fetch(API_BASE + '/api/invoice/' + encodeURIComponent(code), { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }).then(r => r.json()).then(data => { if (!data.ok) { resEl.innerHTML = '
🔍
فاکتور یافت نشد
'; return; } renderInvoice(data, resEl); }).catch(() => { resEl.innerHTML = '
خطا در جستجو
'; }); } function renderInvoice(inv, el) { const settledBadge = inv.settled ? '✅ تسویه شده' : '⏳ تسویه‌نشده'; el.innerHTML = `
📄 فاکتور ${inv.code}
مشتری${inv.cust}
تاریخ${inv.date}
جمع کل${fmtRial(inv.amount)}
وضعیت${settledBadge}
📦 اقلام فاکتور
${(inv.items||[]).map(item => `
${item.name}
${fmtNum(item.qty)} عدد × ${fmtRial(item.price)} ${fmtRial(item.total)}
`).join('')}
`; } // ══════════════════════════════════════ // Price Screen // ══════════════════════════════════════ let _activeGroup = null; function renderPriceScreen() { document.getElementById('price-search').value = ''; const groups = Object.keys(state.priceGroups); const tabsEl = document.getElementById('price-tabs'); _activeGroup = groups[0] || null; tabsEl.innerHTML = `` + groups.map(g => ``).join(''); renderPriceList(); } function setPriceGroup(g, btn) { _activeGroup = g; document.querySelectorAll('#price-tabs .tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); document.getElementById('price-search').value = ''; renderPriceList(); } function filterPrices() { renderPriceList(); } function renderPriceList() { const q = document.getElementById('price-search').value.trim().toLowerCase(); let list = _activeGroup ? (state.priceGroups[_activeGroup]||[]) : state.prices; if (q) list = list.filter(p => p.name.toLowerCase().includes(q) || p.code.toLowerCase().includes(q)); const el = document.getElementById('price-list'); if (!list.length) { el.innerHTML = '
🔍
محصولی یافت نشد
'; return; } el.innerHTML = list.map(p => `
${p.name}
موجودی: ${fmtNum(p.stock)} | کد: ${p.code}
${fmtRial(p.price)}
`).join(''); } // ══════════════════════════════════════ // Settings Screen // ══════════════════════════════════════ let _settingsData = null; async function renderSettingsScreen() { const el = document.getElementById('settings-content'); el.innerHTML = '
در حال بارگذاری...
'; // ویزیتور — فقط مشتریان منطقه‌اش if (!state.isAdmin) { try { const resp = await fetch(API_BASE + '/api/visitor/my-customers', { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }); const data = await resp.json(); if (!data.ok) throw new Error(data.error); _renderVisitorSettings(data); } catch(e) { el.innerHTML = `
خطا: ${e.message}
`; } return; } // مدیر — تنظیمات کامل try { const resp = await fetch(API_BASE + '/api/admin/settings', { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }); _settingsData = await resp.json(); if (!_settingsData.ok) throw new Error(_settingsData.error); _renderSettings(); } catch(e) { el.innerHTML = `
خطا: ${e.message}
`; } } function _renderVisitorSettings(data) { const el = document.getElementById('settings-content'); const customers = data.customers || []; const regions = data.regions || []; el.innerHTML = `
📍 مناطق شما
${regions.length ? regions.join(' | ') : 'منطقه‌ای تنظیم نشده'}
🏪 مشتریان بات (${customers.length} نفر)
${customers.map(c => `
${c.display_name || c.username}
${c.phone || ''}
`).join('') || '
هنوز مشتری ثبت نشده
'}
➕ افزودن مشتری جدید
`; } async function _visitorDeleteCust(username) { if (!confirm(`حذف «${username}» از بات؟`)) return; try { const resp = await fetch(API_BASE + '/api/admin/delete-user', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ username }) }); const data = await resp.json(); if (data.ok) { showToast('✅ حذف شد'); renderSettingsScreen(); } else showToast('❌ ' + (data.error || 'خطا')); } catch(e) { showToast('❌ خطای شبکه'); } } const el = document.getElementById('settings-content'); const s = _settingsData.settings; const users = _settingsData.users; const _MENUS = [ { key: 'bot_customer_balance', label: '💳 مانده حساب' }, { key: 'bot_unsettled_invoices', label: '⏳ فاکتورهای تسویه‌نشده' }, { key: 'bot_invoice_search', label: '🔎 جستجوی فاکتور' }, { key: 'bot_proposal', label: '📦 پیشنهاد فروش' }, { key: 'bot_cheque_info', label: '🏦 چک‌های ثبت نشده' }, { key: 'bot_payesh_customers', label: '🎯 مشتریان پایش' }, { key: 'bot_price_search', label: '💰 قیمت محصولات' }, { key: 'bot_invoice_photos', label: '📷 عکس فاکتورها' }, ]; const DELIVERY_ALLOWED = ['bot_customer_balance','bot_cheque_info','bot_price_search','bot_invoice_photos']; const BOT_DEFAULTS = ALL_MENUS.map(m => m.key); function _userGroup(u) { if (u.user_type === 'customer') return 'customer'; if (u.role === 'تحویل‌دار') return 'delivery'; return 'visitor'; } function _renderUserCard(u) { const group = _userGroup(u); const roleLabel = u.role || (group === 'customer' ? 'مشتری' : 'ویزیتور'); return `
دسترسی بات تلگرام
دسترسی مینی‌اپ
`; } const visitors = users.filter(u => u.username !== 'مدیر' && _userGroup(u) === 'visitor'); const deliveries = users.filter(u => _userGroup(u) === 'delivery'); const customers = users.filter(u => _userGroup(u) === 'customer'); el.innerHTML = `
🔍 جستجوی قیمت
پنهان کردن کالاهای ناموجود (هلو)
در جستجوی قیمت بات
پنهان کردن کالاهای ناموجود (اکسل)
در جستجوی قیمت بات
🔐 دسترسی منوها بر اساس نقش
در حال بارگذاری...
${visitors.length ? `
👤 ویزیتورها
${visitors.map(_renderUserCard).join('')}
` : ''} ${deliveries.length ? `
🚚 تحویل‌داران
${deliveries.map(_renderUserCard).join('')}
` : ''} ${customers.length ? `
🏪 مشتریان
${customers.map(_renderUserCard).join('')}
` : ''}
➕ افزودن کاربر جدید
`; // لود دسترسی نقش‌ها _loadRolePermissions(); } function _toggleSetting(el, key) { el.classList.toggle('on'); _settingsData.settings[key] = el.classList.contains('on'); } function _toggleUser(el, username, field) { el.classList.toggle('on'); const user = _settingsData.users.find(u => u.username === username); if (user) user[field] = el.classList.contains('on'); } function _toggleMenuPerm(el, username, menuKey) { el.classList.toggle('on'); const user = _settingsData.users.find(u => u.username === username); if (!user) return; const BOT_DEFAULTS = ['bot_customer_balance','bot_unsettled_invoices','bot_invoice_search','bot_proposal','bot_cheque_info','bot_payesh_customers','bot_price_search','bot_invoice_photos']; if (!user.permissions) user.permissions = [...BOT_DEFAULTS]; if (el.classList.contains('on')) { if (!user.permissions.includes(menuKey)) user.permissions.push(menuKey); } else { user.permissions = user.permissions.filter(k => k !== menuKey); } } const ALL_MENUS_LIST = [ { key: 'bot_customer_balance', label: '💳 مانده حساب' }, { key: 'bot_unsettled_invoices', label: '⏳ فاکتورهای تسویه‌نشده' }, { key: 'bot_invoice_search', label: '🔎 جستجوی فاکتور' }, { key: 'bot_proposal', label: '📦 پیشنهاد فروش' }, { key: 'bot_cheque_info', label: '🏦 چک‌های ثبت نشده' }, { key: 'bot_payesh_customers', label: '🎯 مشتریان پایش' }, { key: 'bot_price_search', label: '💰 قیمت محصولات' }, { key: 'bot_invoice_photos', label: '📷 عکس فاکتورها' }, ]; const ROLES_LIST = [ { key: 'ویزیتور', icon: '👤' }, { key: 'تحویل‌دار', icon: '🚚' }, { key: 'انباردار', icon: '🏭' }, { key: 'حسابدار', icon: '📒' }, { key: 'مشتری', icon: '🏪' }, ]; let _rolePerms = {}; async function _loadRolePermissions() { try { const resp = await fetch(API_BASE + '/api/admin/role-permissions', { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }); const data = await resp.json(); if (data.ok) { _rolePerms = data.role_permissions || {}; _renderRolePermissions(); } } catch(e) { document.getElementById('role-permissions-section').textContent = '❌ خطا در بارگذاری'; } } function _renderRolePermissions() { const el = document.getElementById('role-permissions-section'); if (!el) return; el.innerHTML = ROLES_LIST.map(role => `
${role.icon} ${role.key}
${ALL_MENUS_LIST.map(m => { const checked = (_rolePerms[role.key] || []).includes(m.key); return `
${m.label}
`; }).join('')}
`).join('') + ``; } function _toggleRolePerm(el, role, menuKey) { el.classList.toggle('on'); if (!_rolePerms[role]) _rolePerms[role] = []; if (el.classList.contains('on')) { if (!_rolePerms[role].includes(menuKey)) _rolePerms[role].push(menuKey); } else { _rolePerms[role] = _rolePerms[role].filter(k => k !== menuKey); } } async function _saveRolePermissions() { try { const resp = await fetch(API_BASE + '/api/admin/role-permissions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ role_permissions: _rolePerms }) }); const data = await resp.json(); if (data.ok) showToast('✅ دسترسی نقش‌ها ذخیره شد'); else showToast('❌ ' + (data.error || 'خطا')); } catch(e) { showToast('❌ خطای شبکه'); } } async function _changePassword(username) { const input = document.getElementById('pw-' + username); const newPw = input?.value?.trim(); if (!newPw) { showToast('⚠️ رمز جدید را وارد کنید'); return; } try { const resp = await fetch(API_BASE + '/api/admin/change-password', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ username, password: newPw }) }); const data = await resp.json(); if (data.ok) { showToast('✅ رمز ' + username + ' تغییر کرد'); if (input) input.value = ''; } else { showToast('❌ ' + (data.error || 'خطا')); } } catch(e) { showToast('❌ خطای شبکه'); } } let _selectedCustomer = null; function _searchNewUser() { const q = document.getElementById('new-user-search').value.trim(); const resEl = document.getElementById('new-user-results'); if (!q || q.length < 2) { resEl.innerHTML = ''; return; } if (!_settingsData || !_settingsData.customers) { resEl.innerHTML = '
در حال بارگذاری...
'; return; } const normalize = s => s.replace(/ي/g,'ی').replace(/ك/g,'ک').toLowerCase(); const qn = normalize(q); const customers = (_settingsData.customers || []).filter(c => normalize(c.name||'').includes(qn) || (c.code||'').includes(q) ).slice(0, 10); if (!customers.length) { resEl.innerHTML = '
نتیجه‌ای یافت نشد
'; return; } let html = ''; for (const c of customers) { const safeName = (c.name||'').replace(/"/g, ''); const safePhone = (c.phone||'').replace(/"/g, ''); html += '
' + (c.name||'') + '' + safePhone + '
'; } resEl.innerHTML = html; } function _selectCustomer(code, name, phone) { _selectedCustomer = { code, name, phone }; document.getElementById('new-user-results').innerHTML = ''; document.getElementById('new-user-search').value = name; const selEl = document.getElementById('new-user-selected'); selEl.style.display = 'block'; selEl.innerHTML = '✅ انتخاب شده: ' + name + ''; if (phone && phone !== 'None' && phone !== 'nan') { document.getElementById('new-user-phone').value = phone; } } async function _addUser() { if (!_selectedCustomer) { showToast('⚠️ ابتدا مشتری را انتخاب کنید'); return; } const phone = document.getElementById('new-user-phone').value.trim(); if (!phone.match(/^09\d{9}$/)) { showToast('⚠️ شماره موبایل معتبر وارد کنید'); return; } try { const resp = await fetch(API_BASE + '/api/admin/add-user', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ name: _selectedCustomer.name, phone, c_code: _selectedCustomer.code }) }); const data = await resp.json(); if (data.ok) { const welcomeMsg = "سلام " + _selectedCustomer.name + " عزیز 👋\n\nشما به سامانه دنیای فیلتر کنکاش اضافه شدید.\n\nبرای استفاده از ربات تلگرام:\n1️⃣ روی لینک زیر کلیک کنید:\nt.me/Kankash_Filter_bot\n2️⃣ دکمه Start را بزنید\n3️⃣ شماره موبایل " + document.getElementById('new-user-phone').value + " را ارسال کنید\n\nموفق باشید 🌟"; _showWelcomeModal(welcomeMsg); _selectedCustomer = null; document.getElementById('new-user-search').value = ''; document.getElementById('new-user-phone').value = ''; document.getElementById('new-user-selected').style.display = 'none'; await renderSettingsScreen(); } else { showToast('❌ ' + (data.error || 'خطا')); } } catch(e) { showToast('❌ خطای شبکه'); } } function _showWelcomeModal(msg) { document.getElementById('welcome-text').value = msg; document.getElementById('welcome-modal').style.display = 'flex'; } function _copyWelcome() { const txt = document.getElementById('welcome-text').value; navigator.clipboard.writeText(txt).then(() => { showToast('✅ کپی شد'); }).catch(() => { document.getElementById('welcome-text').select(); document.execCommand('copy'); showToast('✅ کپی شد'); }); } function _filterUsers() { const q = document.getElementById('user-search').value.trim().toLowerCase(); document.querySelectorAll('.user-settings-card').forEach(card => { const name = card.querySelector('.user-settings-name').textContent.toLowerCase(); card.style.display = (!q || name.includes(q)) ? 'block' : 'none'; }); } async function _deleteUser(username) { if (!confirm('حذف ' + username + '؟')) return; try { const resp = await fetch(API_BASE + '/api/admin/delete-user', { method: 'POST', headers: {'Content-Type':'application/json','X-Token':state.token,'X-Username':encodeURIComponent(state.username),'ngrok-skip-browser-warning':'1'}, body: JSON.stringify({username}) }); const data = await resp.json(); if (data.ok) { showToast('✅ حذف شد'); await renderSettingsScreen(); } else showToast('❌ ' + (data.error||'خطا')); } catch(e) { showToast('❌ خطای شبکه'); } } function _filterUsers() { const q = document.getElementById('user-search').value.trim().toLowerCase(); document.querySelectorAll('.user-settings-card').forEach(card => { const name = card.querySelector('.user-settings-name').textContent.toLowerCase(); card.style.display = (!q || name.includes(q)) ? 'block' : 'none'; }); } async function _deleteUser(username) { if (!confirm('حذف ' + username + '؟')) return; try { const resp = await fetch(API_BASE + '/api/admin/delete-user', { method: 'POST', headers: {'Content-Type':'application/json','X-Token':state.token,'X-Username':encodeURIComponent(state.username),'ngrok-skip-browser-warning':'1'}, body: JSON.stringify({username}) }); const data = await resp.json(); if (data.ok) { showToast('✅ حذف شد'); await renderSettingsScreen(); } else showToast('❌ ' + (data.error||'خطا')); } catch(e) { showToast('❌ خطای شبکه'); } } let _otpPhone = ''; async function sendOtp() { const phone = document.getElementById('otp-phone').value.trim(); if (!phone.match(/^09\d{9}$/)) { showOtpError('شماره معتبر نیست'); return; } _otpPhone = phone; try { const resp = await fetch(API_BASE + '/api/send-otp', { method: 'POST', headers: {'Content-Type':'application/json','ngrok-skip-browser-warning':'1'}, body: JSON.stringify({phone}) }); const data = await resp.json(); if (data.ok) { document.getElementById('otp-step1').style.display = 'none'; document.getElementById('otp-step2').style.display = 'block'; document.getElementById('otp-error').style.display = 'none'; } else { showOtpError(data.error || 'خطا'); } } catch(e) { showOtpError('خطای شبکه'); } } async function verifyOtp() { const code = document.getElementById('otp-code').value.trim(); if (code.length !== 6) { showOtpError('کد ۶ رقمی وارد کنید'); return; } try { const resp = await fetch(API_BASE + '/api/verify-otp', { method: 'POST', headers: {'Content-Type':'application/json','ngrok-skip-browser-warning':'1'}, body: JSON.stringify({phone: _otpPhone, code}) }); const data = await resp.json(); if (data.ok) { state.token = data.token; state.username = data.username; state.displayName = data.display_name; state.isAdmin = data.is_admin; state.priceTier = data.price_tier; state.regions = data.regions; state.userType = data.user_type; await loadBackgroundData(); showScreen('menu'); buildMainMenu(); } else { showOtpError(data.error || 'کد اشتباه است'); } } catch(e) { showOtpError('خطای شبکه'); } } function showOtpError(msg) { const el = document.getElementById('otp-error'); el.textContent = msg; el.style.display = 'block'; } async function _saveSettings() { try { const resp = await fetch(API_BASE + '/api/admin/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' }, body: JSON.stringify({ settings: _settingsData.settings, users: _settingsData.users.map(u => ({ username: u.username, telegram_enabled: u.telegram_enabled, webapp_enabled: u.webapp_enabled, permissions: u.permissions || null, })), }) }); const data = await resp.json(); if (data.ok) { showToast('✅ تنظیمات ذخیره شد'); } else { showToast('❌ خطا: ' + data.error); } } catch(e) { showToast('❌ خطای شبکه'); } } // ══════════════════════════════════════ // Catalog Screen // ══════════════════════════════════════ let _catalogItems = []; let _catalogFiltered = []; let _catalogGroup = null; async function renderCatalogScreen() { document.getElementById('catalog-search').value = ''; _catalogGroup = null; // اگه قبلاً بارگذاری شده، دوباره از سرور نگیر if (_catalogItems.length > 0) { _buildCatalogTabs(); _catalogFiltered = _catalogItems; _renderCatalogGrid(_catalogItems); return; } if (DEMO_MODE) { // Demo data _catalogItems = state.prices.map(p => ({ A_Code: p.code, A_Name: p.name, A_Code_C: p.code, group: p.group, price: p.price, stock: p.stock, pic_filename: null, unit: 'عدد', carton: null, })); _buildCatalogTabs(); _catalogFiltered = _catalogItems; _renderCatalogGrid(_catalogItems); return; } document.getElementById('catalog-grid').innerHTML = '
در حال بارگذاری...
'; document.getElementById('catalog-tabs').innerHTML = ''; try { const resp = await fetch(API_BASE + '/api/catalog', { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }); const data = await resp.json(); _catalogItems = data.items || []; _buildCatalogTabs(); _catalogFiltered = _catalogItems; _renderCatalogGrid(_catalogItems); } catch(e) { document.getElementById('catalog-grid').innerHTML = '
خطا در بارگذاری
'; } } function _buildCatalogTabs() { const groups = [...new Set(_catalogItems.map(p => p.group || 'سایر'))]; const tabsEl = document.getElementById('catalog-tabs'); tabsEl.innerHTML = `` + groups.map(g => ``).join(''); } function _setCatalogGroup(g, btn) { _catalogGroup = g; document.querySelectorAll('#catalog-tabs .tab-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); document.getElementById('catalog-search').value = ''; const list = g ? _catalogItems.filter(p => (p.group||'سایر') === g) : _catalogItems; _catalogFiltered = list; _renderCatalogGrid(list); } function filterCatalog() { const q = document.getElementById('catalog-search').value.trim().toLowerCase(); let list = _catalogGroup ? _catalogItems.filter(p => (p.group||'سایر') === _catalogGroup) : _catalogItems; if (q) list = list.filter(p => (p.A_Name||'').toLowerCase().includes(q) || (p.A_Code||'').toLowerCase().includes(q) || (p.A_Code_C||'').toLowerCase().includes(q) ); _catalogFiltered = list; _renderCatalogGrid(list); } function _renderCatalogGrid(list) { const el = document.getElementById('catalog-grid'); if (!list.length) { el.innerHTML = '
🔍
کالایی یافت نشد
'; return; } el.innerHTML = list.map((p, idx) => { const imgHtml = p.pic_filename ? `${p.A_Name}` + `` : `
🛢
`; const priceStr = p.price ? fmtRial(p.price) : '—'; const stockCls = (!p.stock || p.stock <= 0) ? 'out' : ''; const stockStr = p.stock ? fmtNum(p.stock) + ' ' + (p.unit||'') : 'ناموجود'; return `
${imgHtml}
${p.A_Name}
کد: ${p.A_Code_C || p.A_Code}
${priceStr}
📦 ${stockStr}
`; }).join(''); } function openKalaDetail(idx) { const p = _catalogFiltered[idx]; if (!p) return; navigate('catalog-detail'); document.getElementById('catalog-detail-title').textContent = p.A_Name; const imgHtml = p.pic_filename ? `${p.A_Name}` : `
🛢
`; const priceStr = p.price ? fmtNum(p.price) + ' ریال' : '—'; const stockStr = p.stock != null ? fmtNum(p.stock) + ' ' + (p.unit||'') : '—'; const stockCls = (!p.stock || p.stock <= 0) ? 'color:var(--red)' : 'color:var(--green)'; let attrs = [ { k: 'کد کالا', v: p.A_Code_C || p.A_Code, cls: 'code-val' }, { k: 'گروه', v: p.group || '—' }, { k: 'قیمت', v: priceStr, cls: 'price-val' }, ...(p.discount > 0 ? [{ k: 'تخفیف', v: p.discount + '٪' }, { k: 'قیمت با تخفیف', v: (p.price * (1 - p.discount/100)).toLocaleString('fa-IR') + ' ریال' }] : []), { k: 'موجودی', v: `${stockStr}` }, ]; if (p.carton) attrs.push({ k: 'تعداد کارتن', v: p.carton }); if (p.disc) attrs.push({ k: 'تخفیف', v: p.disc + '٪' }); document.getElementById('catalog-detail-content').innerHTML = ` ${imgHtml}
📦 ${p.A_Name}
${attrs.map(a => `
${a.k}${a.v}
`).join('')}
`; } // ══════════════════════════════════════ // Sales Screen // ══════════════════════════════════════ let _salesPeriod = 'today'; function setSalesPeriod(period, btn) { _salesPeriod = period; document.querySelectorAll('.period-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); renderSalesContent(); } function renderSalesScreen(period) { _salesPeriod = period || 'today'; renderSalesContent(); } function renderSalesContent() { const el = document.getElementById('sales-content'); if (DEMO_MODE) { const demo = { today: { sales:12500000, count:3, commission:375000, label:'امروز' }, week: { sales:98000000, count:24, commission:2940000, label:'۷ روز اخیر' }, month: { sales:345000000, count:87, commission:10350000, label:'این ماه شمسی' }, }; const d = demo[_salesPeriod] || demo.today; el.innerHTML = `
📊 عملکرد — ${d.label}
جمع فروش
${fmtRial(d.sales)}
تعداد فاکتور
${fmtNum(d.count)}
💵 پورسانت
${fmtRial(d.commission)}
پورسانت دوره ${d.label}
`; return; } el.innerHTML = '
'; fetch(API_BASE + '/api/sales?period=' + _salesPeriod, { headers: { 'X-Token': state.token, 'X-Username': encodeURIComponent(state.username), 'ngrok-skip-browser-warning': '1' } }).then(r => r.json()).then(data => { el.innerHTML = `
📊 عملکرد — ${data.label}
جمع فروش${fmtRial(data.sales)}
تعداد فاکتور${fmtNum(data.count)}
پورسانت${fmtRial(data.commission)}
`; }).catch(() => el.innerHTML = '
خطا
'); } // ══════════════════════════════════════ // Utility // ══════════════════════════════════════ function delay(ms) { return new Promise(r => setTimeout(r, ms)); } // Enter key on login document.getElementById('login-phone').addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); }); // Init — بررسی sessionStorage برای ورود خودکار (async function() { try { const savedToken = sessionStorage.getItem('kankash_token'); const savedUsername = sessionStorage.getItem('kankash_username'); if (savedToken && savedUsername) { // تأیید token با سرور const resp = await fetch(API_BASE + '/api/data', { headers: { 'X-Token': savedToken, 'X-Username': encodeURIComponent(savedUsername), 'ngrok-skip-browser-warning': '1' } }); if (resp.ok) { state.token = savedToken; state.username = savedUsername; state.displayName = sessionStorage.getItem('kankash_display') || savedUsername; state.regions = JSON.parse(sessionStorage.getItem('kankash_regions') || '[]'); state.isAdmin = sessionStorage.getItem('kankash_isAdmin') === '1'; state.userType = sessionStorage.getItem('kankash_userType') || ''; state.priceTier = parseInt(sessionStorage.getItem('kankash_priceTier') || '1'); const data = await resp.json(); if (data.customers) state.customers = data.customers; if (data.unsettled) state.unsettledData = data.unsettled; if (data.prices) state.prices = data.prices; if (data.price_groups) state.priceGroups = data.price_groups; document.getElementById('welcome-name').textContent = state.displayName; document.getElementById('welcome-region').textContent = state.isAdmin ? '👑 مدیر سیستم' : ('📍 ' + state.regions.slice(0,2).join(' | ')); document.getElementById('header-sub').textContent = state.isAdmin ? '👑 ' + state.displayName : '👤 ' + state.displayName; buildMainMenu(); showScreen('menu'); return; } } } catch(e) {} // اگه token معتبر نبود، صفحه login showScreen('login'); })();