יש איזה אפשרות (תוסף?) שאפשר לראות את הסליקות אשראי?
-
תודה רבה
מועדים לשמחה! -
תוכנה לדוחות סליקה
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>דוח עסקאות אשראי - LogCreditCardOK | ימות המשיח</title> <style> :root { --primary: #2563eb; --primary-hover: #1d4ed8; --primary-light: #eff6ff; --success: #10b981; --warning: #f59e0b; --danger: #ef4444; --bg: #f8fafc; --card-bg: #ffffff; --text: #0f172a; --text-muted: #64748b; --border: #e2e8f0; --radius: 12px; --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.07); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); } * { box-sizing: border-box; margin: 0; padding: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Heebo', Arial, sans-serif; } body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; } header { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); color: #fff; padding: 0.85rem 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .brand { display: flex; align-items: center; gap: 0.75rem; } .brand-icon { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 1.3rem; box-shadow: 0 2px 8px rgba(16,185,129,0.3); } .brand-title h1 { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.2px; } .brand-title p { font-size: 0.75rem; color: #94a3b8; font-family: monospace; } .header-actions { display: flex; align-items: center; gap: 0.75rem; } .settings-btn-pill { display: inline-flex; align-items: center; gap: 0.6rem; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.18); color: #f8fafc; padding: 0.5rem 1.1rem; border-radius: 30px; cursor: pointer; font-size: 0.88rem; font-weight: 600; backdrop-filter: blur(8px); transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(0,0,0,0.12); } .settings-btn-pill:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.35); transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,0.2); } .settings-btn-pill .gear-icon { font-size: 1.1rem; } .status-indicator-dot { width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; box-shadow: 0 0 6px #f59e0b; transition: background 0.3s; } .status-indicator-dot.active { background: #10b981; box-shadow: 0 0 8px #10b981; } .container { max-width: 1440px; width: 100%; margin: 0 auto; padding: 1.5rem; flex: 1; } .card { background: var(--card-bg); border-radius: var(--radius); border: 1px solid var(--border); box-shadow: var(--shadow); padding: 1.25rem; margin-bottom: 1.5rem; } .clean-action-bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; } .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.65rem 1.25rem; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; transition: all 0.2s; font-size: 0.95rem; white-space: nowrap; } .btn-primary { background: var(--primary); color: #fff; } .btn-primary:hover { background: var(--primary-hover); } .btn-success { background: var(--success); color: #fff; } .btn-success:hover { background: #059669; } .btn-secondary { background: #f1f5f9; color: #334155; border: 1px solid var(--border); } .btn-secondary:hover { background: #e2e8f0; } .form-control { padding: 0.65rem 0.85rem; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; background-color: #fff; transition: border-color 0.2s; } .form-control:focus { outline: none; border-color: var(--primary); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; } .stat-card { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.25rem; box-shadow: var(--shadow); } .stat-label { font-size: 0.8rem; color: var(--text-muted); font-weight: 500; } .stat-value { font-size: 1.6rem; font-weight: 800; color: var(--text); } .stat-value.amount { color: #059669; } .filter-panel { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; margin-bottom: 1.5rem; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 1rem; } .filter-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; } .quick-dates { display: flex; flex-wrap: wrap; gap: 0.4rem; } .chip-btn { border: 1px solid var(--border); background: #fff; padding: 0.35rem 0.75rem; border-radius: 20px; font-size: 0.8rem; font-weight: 500; cursor: pointer; transition: all 0.15s; } .chip-btn:hover { background: var(--primary-light); border-color: var(--primary); color: var(--primary); } .chip-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; } .sort-indicator-badge { display: inline-flex; align-items: center; gap: 0.3rem; background: #f1f5f9; color: #475569; padding: 0.25rem 0.6rem; border-radius: 6px; font-size: 0.8rem; font-weight: 600; } .table-responsive { overflow-x: auto; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); position: relative; max-height: 65vh; } table { width: 100%; border-collapse: collapse; text-align: right; font-size: 0.9rem; } thead { position: sticky; top: 0; background: #f8fafc; z-index: 10; border-bottom: 2px solid var(--border); } th { padding: 0.85rem 1rem; font-weight: 700; color: #334155; user-select: none; cursor: pointer; white-space: nowrap; } th:hover { background: #f1f5f9; } th .sort-icon { font-size: 0.75rem; margin-right: 0.35rem; color: #94a3b8; font-weight: 800; } th.sorted-col .sort-icon { color: var(--primary); } td { padding: 0.75rem 1rem; border-bottom: 1px solid #f1f5f9; white-space: nowrap; max-width: 300px; overflow: hidden; text-overflow: ellipsis; } tr:hover td { background-color: #f8fafc; } tr { cursor: pointer; } .badge-payment-for { background: #e0f2fe; color: #0369a1; padding: 0.25rem 0.65rem; border-radius: 6px; font-weight: 700; font-size: 0.85rem; display: inline-block; border: 1px solid #bae6fd; } .pagination-bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-top: 1rem; padding: 0.5rem 0; } .page-nav { display: flex; align-items: center; gap: 0.3rem; } .page-btn { min-width: 34px; height: 34px; padding: 0 0.5rem; border: 1px solid var(--border); background: #fff; border-radius: 6px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-weight: 500; font-size: 0.85rem; } .page-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); } .page-btn:disabled { opacity: 0.4; cursor: not-allowed; } .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 23, 42, 0.65); backdrop-filter: blur(4px); display: none; align-items: center; justify-content: center; z-index: 1000; padding: 1rem; } .modal-overlay.active { display: flex; } .modal { background: #fff; border-radius: var(--radius); width: 100%; max-width: 620px; max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lg); display: flex; flex-direction: column; } .modal-header { padding: 1.25rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; } .modal-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; } .close-btn { background: transparent; border: none; font-size: 1.5rem; cursor: pointer; color: var(--text-muted); } .banner { padding: 0.85rem 1.1rem; border-radius: 8px; margin-bottom: 1rem; font-size: 0.9rem; display: none; line-height: 1.5; } .banner-danger { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; } .banner-success { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; } .banner-warning { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; } .modal-quick-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.4rem; } .modal-chip { background: #f1f5f9; border: 1px solid var(--border); padding: 0.25rem 0.55rem; border-radius: 6px; font-size: 0.78rem; cursor: pointer; font-family: monospace; direction: ltr; } .modal-chip:hover { background: var(--primary-light); border-color: var(--primary); color: var(--primary); } .spinner { border: 2px solid #f3f3f3; border-top: 2px solid #fff; border-radius: 50%; width: 16px; height: 16px; animation: spin 0.8s linear infinite; display: inline-block; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> </head> <body> <!-- כותרת ראשית --> <header> <div class="brand"> <div class="brand-icon">💳</div> <div class="brand-title"> <h1>דוח עסקאות אשראי - LogCreditCardOK</h1> <p>שליפת קובץ YMGR ישירות בפקודת RenderYMGRFile</p> </div> </div> <!-- כפתור הגדרות מעוצב עם סטטוס חיבור --> <div class="header-actions"> <button class="settings-btn-pill" onclick="openConfigModal()" title="לחץ להגדרת נתיב, קובץ ופרטי API"> <span class="status-indicator-dot" id="headerStatusDot"></span> <span class="gear-icon">⚙️</span> <span id="headerStatusText">הגדרות API</span> </button> </div> </header> <div class="container"> <div id="bannerBox" class="banner"></div> <!-- דשבורד ראשי נקי (ללא שדות נתיב וקבצים) --> <div class="card"> <div class="clean-action-bar"> <div> <h2 style="font-size: 1.15rem; font-weight: 700; margin-bottom: 0.25rem;">ניהול וצפייה בעסקאות אשראי</h2> <p style="font-size: 0.85rem; color: var(--text-muted);"> שליפה ישירה מול המערכת, סינון תאריכים מתקדם וחישוב סכומים אוטומטי. </p> </div> <div style="display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;"> <button class="btn btn-secondary" onclick="document.getElementById('fileUploadInput').click()"> 📁 טען קובץ ידנית </button> <input type="file" id="fileUploadInput" accept=".ymgr,.csv,.json,.txt" style="display: none;" onchange="handleLocalFile(event)"> <button id="fetchBtn" class="btn btn-success" style="padding: 0.75rem 1.6rem; font-size: 1rem;" onclick="fetchCreditCardReport()"> <span>⚡ שלוף עסקאות אשראי</span> </button> </div> </div> </div> <!-- כרטיסי סיכום וסטטיסטיקה --> <div class="stats-grid"> <div class="stat-card"> <span class="stat-label">סה"כ עסקאות</span> <span class="stat-value" id="statTotalRows">0</span> </div> <div class="stat-card"> <span class="stat-label">עסקאות מסוננות</span> <span class="stat-value" id="statFilteredRows" style="color: var(--primary);">0</span> </div> <div class="stat-card"> <span class="stat-label">סה"כ סכום עסקאות (₪)</span> <span class="stat-value amount" id="statTotalSum">₪0.00</span> </div> <div class="stat-card"> <span class="stat-label">תורמים / טלפונים ייחודיים</span> <span class="stat-value" id="statUniquePhones">0</span> </div> </div> <!-- סרגל סינונים וטווח תאריכים --> <div class="filter-panel"> <div class="filter-row"> <div class="form-group" style="min-width: 140px;"> <label for="dateColumnSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">עמודת תאריך</label> <select id="dateColumnSelect" class="form-control" onchange="applyFilters()"> <option value="">(זיהוי אוטומטי)</option> </select> </div> <div class="form-group"> <label for="dateFrom" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">מתאריך</label> <input type="date" id="dateFrom" class="form-control" onchange="applyFilters()"> </div> <div class="form-group"> <label for="dateTo" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">עד תאריך</label> <input type="date" id="dateTo" class="form-control" onchange="applyFilters()"> </div> <div class="form-group" style="flex: 1;"> <label style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">טווחים מהירים</label> <div class="quick-dates"> <button class="chip-btn" onclick="setQuickDate('today')">היום</button> <button class="chip-btn" onclick="setQuickDate('yesterday')">אתמול</button> <button class="chip-btn" onclick="setQuickDate('7days')">7 ימים אחרונים</button> <button class="chip-btn" onclick="setQuickDate('30days')">30 ימים אחרונים</button> <button class="chip-btn" onclick="setQuickDate('month')">החודש הנוכחי</button> <button class="chip-btn" onclick="setQuickDate('all')">הכל</button> </div> </div> </div> <div class="filter-row"> <div class="form-group" style="flex: 2; min-width: 240px;"> <label for="globalSearch" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">חיפוש חופשי (טלפון, 4 ספרות, אישור, שם, שלוחה...)</label> <input type="text" id="globalSearch" class="form-control" placeholder="חפש לפי מספר טלפון, סכום, מספר אישור..." oninput="applyFilters()"> </div> <div class="form-group" style="flex: 1; min-width: 160px;"> <label for="specificColSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">סינון לפי עמודה</label> <select id="specificColSelect" class="form-control" onchange="applyFilters()"> <option value="">כל העמודות</option> </select> </div> <div class="form-group" style="flex: 1; min-width: 160px;"> <label for="specificColValue" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">ערך בעמודה</label> <input type="text" id="specificColValue" class="form-control" placeholder="סנן ערך..." oninput="applyFilters()"> </div> <div class="form-group" style="display: flex; align-items: center; justify-content: flex-end; padding-top: 1.4rem;"> <label style="display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; cursor: pointer;"> <input type="checkbox" id="decodeYemotCharsCheck" checked onchange="renderTable()"> פענוח תווים (^ ו-*) </label> </div> </div> <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.75rem; border-top: 1px solid var(--border); padding-top: 0.75rem;"> <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> <button class="btn btn-secondary" onclick="exportData('csv')">📥 ייצוא ל-Excel / CSV</button> <button class="btn btn-secondary" onclick="exportData('json')">📄 ייצוא JSON</button> <button class="btn btn-secondary" onclick="window.print()">🖨️ הדפסה</button> <button class="btn btn-secondary" onclick="resetFilters()">🔄 איפוס סינונים</button> </div> <div style="display: flex; gap: 0.75rem; align-items: center;"> <span class="sort-indicator-badge">סדר תצוגה: מהחדש לישן ▼</span> <select id="pageSizeSelect" class="form-control" style="width: auto;" onchange="changePageSize()"> <option value="15">15 שורות לעמוד</option> <option value="25" selected>25 שורות לעמוד</option> <option value="50">50 שורות לעמוד</option> <option value="100">100 שורות לעמוד</option> <option value="all">כל השורות</option> </select> </div> </div> </div> <!-- טבלת נתונים --> <div class="table-responsive"> <table id="dataTable"> <thead id="tableHead"> <tr> <th style="width: 50px; text-align: center;">#</th> <th>אין נתונים להצגה - לחצו על כפתור השליפה למעלה</th> </tr> </thead> <tbody id="tableBody"> <tr> <td colspan="2" style="text-align: center; padding: 3rem; color: var(--text-muted);"> נא לוודא שהטוקן מוגדר ב-⚙️ הגדרות, וללחוץ על <strong>"⚡ שלוף עסקאות אשראי"</strong> </td> </tr> </tbody> </table> </div> <!-- דפדוף --> <div class="pagination-bar"> <div id="paginationInfo" style="font-size: 0.85rem; color: var(--text-muted);">מציג 0-0 מתוך 0 עסקאות</div> <div class="page-nav" id="paginationNav"></div> </div> </div> <!-- חלון הגדרות API מרוכז (כולל שינוי נתיב ושם קובץ) --> <div class="modal-overlay" id="configModal" onclick="closeConfigModal(event)"> <div class="modal" onclick="event.stopPropagation()"> <div class="modal-header"> <h3 style="display: flex; align-items: center; gap: 0.5rem;"> <span>⚙️</span> הגדרות API ונתיבי קבצים </h3> <button class="close-btn" onclick="closeConfigModal()">×</button> </div> <div class="modal-body"> <div class="form-group"> <label for="cfgServerSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">שרת API</label> <select id="cfgServerSelect" class="form-control" onchange="toggleCustomServerInput()"> <option value="https://www.call2all.co.il/ym/api/">שרת ראשי (www.call2all.co.il)</option> <option value="https://private.call2all.co.il/ym/api/">שרת פרטי (private.call2all.co.il)</option> <option value="custom">כתובת מותאמת אישית...</option> </select> </div> <div class="form-group" id="cfgCustomServerWrap" style="display: none;"> <label for="cfgCustomServerUrl" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">כתובת שרת מותאמת</label> <input type="text" id="cfgCustomServerUrl" class="form-control" placeholder="https://your-server.com/api/"> </div> <div class="form-group"> <label for="cfgTokenInput" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">טוקן הזדהות / מספר מערכת וסיסמה (token)</label> <input type="password" id="cfgTokenInput" class="form-control" placeholder="למשל: 0771234567:1234 או API Key"> <small style="color: var(--text-muted); font-size: 0.78rem;">דוגמה: 0771234567:1234 או מפתח API מחומת האש.</small> </div> <!-- הגדרת נתיב ושם קובץ ה-YMGR רק כאן בהגדרות --> <div class="form-group" style="border-top: 1px solid var(--border); padding-top: 0.85rem;"> <label for="cfgPathInput" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;"> נתיב ושם קובץ ה-YMGR לשליפה (פרמטר wath): </label> <input type="text" id="cfgPathInput" class="form-control" value="ivr2:/Log/LogCreditCardOK.ymgr" style="direction: ltr; font-family: monospace;"> <div class="modal-quick-chips"> <span style="font-size: 0.78rem; color: var(--text-muted); align-self: center;">קיצורים לבחירה:</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/Log/LogCreditCardOK.ymgr'">ivr2:/Log/LogCreditCardOK.ymgr (ראשי)</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/LogCreditCardOK.ymgr'">ivr2:/LogCreditCardOK.ymgr (שורש)</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/1/LogCreditCardOK.ymgr'">ivr2:/1/LogCreditCardOK.ymgr (שלוחה 1)</span> </div> </div> <!-- שמות שלוחות ידידותיים --> <div class="form-group" style="border-top: 1px solid var(--border); padding-top: 0.85rem;"> <label for="cfgExtensionAliases" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;"> שמות מותאמים לעמודת "תשלום ל..." (אופציונלי): </label> <input type="text" id="cfgExtensionAliases" class="form-control" placeholder="לדוגמה: 1:תרומות כללי, 2:קרן עזרה, ראשי:קופה ראשית"> <small style="color: var(--text-muted); font-size: 0.78rem;">פורמט: מספר_שלוחה:שם_השלוחה, מופרד בפסיקים.</small> </div> <div style="display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.75rem;"> <button class="btn btn-secondary" onclick="closeConfigModal()">סגור</button> <button class="btn btn-primary" onclick="saveConfiguration()">שמור הגדרות</button> </div> </div> </div> </div> <!-- חלונית פירוט רשומה --> <div class="modal-overlay" id="detailModal" onclick="closeModal(event)"> <div class="modal" onclick="event.stopPropagation()"> <div class="modal-header"> <h3 id="modalTitle">פרטי עסקה</h3> <button class="close-btn" onclick="closeModal()">×</button> </div> <div class="modal-body" id="modalContent"></div> </div> </div> <script> let appConfig = { server: 'https://www.call2all.co.il/ym/api/', customServer: '', token: '', filePath: 'ivr2:/Log/LogCreditCardOK.ymgr', extensionAliases: '' }; let rawData = []; let filteredData = []; let allColumns = []; let visibleColumns = []; let currentSort = { col: null, asc: false }; // ברירת מחדל: מהחדש לישן let currentPage = 1; let pageSize = 25; let autoDateCol = null; let autoTimeCol = null; let amountCol = null; window.addEventListener('DOMContentLoaded', () => { loadAppConfig(); }); function loadAppConfig() { try { const saved = localStorage.getItem('ym_credit_card_cfg_direct_v7'); if (saved) { appConfig = Object.assign(appConfig, JSON.parse(saved)); } } catch (e) {} updateConnectionStatusBadge(); } function updateConnectionStatusBadge() { const dot = document.getElementById('headerStatusDot'); const txt = document.getElementById('headerStatusText'); if (appConfig.token && appConfig.token.trim()) { dot.className = 'status-indicator-dot active'; let preview = appConfig.token.includes(':') ? appConfig.token.split(':')[0] : 'מוגדר'; txt.textContent = `מחובר (${preview})`; } else { dot.className = 'status-indicator-dot'; txt.textContent = 'הגדרות API'; } } function saveConfigQuietly() { try { localStorage.setItem('ym_credit_card_cfg_direct_v7', JSON.stringify(appConfig)); } catch (e) {} } function openConfigModal() { document.getElementById('cfgServerSelect').value = appConfig.server; document.getElementById('cfgCustomServerUrl').value = appConfig.customServer || ''; document.getElementById('cfgTokenInput').value = appConfig.token || ''; document.getElementById('cfgPathInput').value = appConfig.filePath || 'ivr2:/Log/LogCreditCardOK.ymgr'; document.getElementById('cfgExtensionAliases').value = appConfig.extensionAliases || ''; toggleCustomServerInput(); document.getElementById('configModal').classList.add('active'); } function closeConfigModal(e) { if (!e || e.target.classList.contains('modal-overlay') || e.target.classList.contains('close-btn') || e.target.tagName === 'BUTTON') { document.getElementById('configModal').classList.remove('active'); } } function toggleCustomServerInput() { const val = document.getElementById('cfgServerSelect').value; document.getElementById('cfgCustomServerWrap').style.display = val === 'custom' ? 'block' : 'none'; } function saveConfiguration() { appConfig.server = document.getElementById('cfgServerSelect').value; appConfig.customServer = document.getElementById('cfgCustomServerUrl').value.trim(); appConfig.token = document.getElementById('cfgTokenInput').value.trim(); appConfig.filePath = document.getElementById('cfgPathInput').value.trim() || 'ivr2:/Log/LogCreditCardOK.ymgr'; appConfig.extensionAliases = document.getElementById('cfgExtensionAliases').value.trim(); saveConfigQuietly(); updateConnectionStatusBadge(); closeConfigModal(); showBanner('ההגדרות והנתיב נשמרו בהצלחה!', 'success'); if (rawData.length > 0) { applyExtensionNamesToData(appConfig.filePath); applyFilters(); } } function showBanner(text, type = 'danger') { const b = document.getElementById('bannerBox'); b.className = `banner banner-${type}`; b.style.display = 'block'; b.innerHTML = text; b.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function hideBanner() { document.getElementById('bannerBox').style.display = 'none'; } // מנגנון סינון גורף ומוחלט של כל העמודות שביקשת להסיר function isFieldExcluded(fieldName) { if (!fieldName) return false; const clean = fieldName.trim().toLowerCase().replace(/[\u200e\u200f\ufeff"']/g, ''); // 1. מערכת, מספר מערכת, DID if (clean === 'מערכת' || clean.includes('מספר מערכת') || clean === 'did' || clean === 'apidid') return true; // 2. מספר מערכת אליו חייגו, RealDID, מספר מחויג if (clean.includes('חייגו') || clean.includes('מחויג') || clean.includes('מחוייג') || clean.includes('realdid') || clean.includes('מספר משנה')) return true; // 3. חברת סליקה, סוג סליקה, חברה סולקת if (clean.includes('סליקה') || clean.includes('סולקת') || clean === 'חברה' || clean === 'ספק' || clean.includes('credit_card_type')) return true; // 4. מסוף, טרמינל if (clean.includes('מסוף') || clean.includes('terminal')) return true; // 5. מטבע, סוג מטבע if (clean.includes('מטבע') || clean.includes('currency')) return true; // 6. cvv, קוד אבטחה if (clean.includes('cvv') || clean.includes('אבטחה')) return true; // 7. הערות, הערה if (clean.includes('הערה') || clean.includes('הערות') || clean.includes('comment') || clean.includes('remark') || clean.includes('note')) return true; // 8. תוקף כרטיס, תוקף if (clean.includes('תוקף') || clean.includes('expdate') || clean.includes('expiration')) return true; // 9. סוג זיהוי if (clean.includes('סוג זיהוי') || clean.includes('סוג התחברות') || clean.includes('enteridtype')) return true; // 10. מספר זיהוי, תעודת זהות, ת.ז if (clean.includes('מספר זיהוי') || clean.includes('תעודת זהות') || clean.includes('ת.ז') || clean === 'enterid' || clean === 'id') return true; // שלוחה מקורית (מוחלפת על ידי עמודת "תשלום ל...") if (clean === 'שלוחה' || clean === 'תיקיה' || clean === 'תיקייה' || clean === 'apiextension' || clean === 'folder') return true; return false; } // שליפה ישירה מ-API של ימות המשיח async function fetchCreditCardReport() { hideBanner(); if (!appConfig.token || !appConfig.token.trim()) { showBanner('נא להגדיר תחילה את הטוקן בלחיצה על כפתור <strong>⚙️ הגדרות API</strong> בראש הדף.', 'warning'); openConfigModal(); return; } const baseUrl = appConfig.server === 'custom' ? appConfig.customServer : appConfig.server; const cleanBase = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'; const endpoint = `${cleanBase}RenderYMGRFile`; const btn = document.getElementById('fetchBtn'); const origText = btn.innerHTML; btn.innerHTML = `<span class="spinner"></span> שולף עסקאות...`; btn.disabled = true; const chosenPath = appConfig.filePath || 'ivr2:/Log/LogCreditCardOK.ymgr'; let result = await callDirectApi(endpoint, chosenPath); // ניסיון חלופי אם הקובץ לא נמצא בנתיב המוגדר if (!result.success && result.isFileNotFound) { let altPath = chosenPath.includes('/Log/') ? chosenPath.replace('/Log/', '/') : chosenPath.replace('ivr2:/', 'ivr2:/Log/'); btn.innerHTML = `<span class="spinner"></span> בודק נתיב חלופי...`; let altResult = await callDirectApi(endpoint, altPath); if (altResult.success) { result = altResult; appConfig.filePath = altPath; saveConfigQuietly(); } } btn.innerHTML = origText; btn.disabled = false; if (result.success && result.rows.length > 0) { loadData(result.rows, result.usedPath); showBanner(`דוח עסקאות האשראי נשלף בהצלחה! נטענו <strong>${result.rows.length.toLocaleString('he-IL')}</strong> עסקאות (מוצגות מהחדש לישן).`, 'success'); } else { let errDesc = result.errorDetails || 'הקובץ לא נמצא או שהתקבלה תשובה ריקה.'; showBanner(`<strong>שגיאה בשליפת הדוח:</strong><br>${errDesc}`, 'danger'); } } async function callDirectApi(endpoint, path) { const params = new URLSearchParams(); params.append('token', appConfig.token); params.append('wath', path); params.append('convertType', 'json'); params.append('notLoadLang', '0'); const fullUrl = `${endpoint}?${params.toString()}`; try { const response = await fetch(fullUrl, { method: 'GET' }); if (!response.ok) { return { success: false, requestUrl: fullUrl, usedPath: path, errorDetails: `HTTP ${response.status} (${response.statusText})` }; } const text = await response.text(); let parsed = null; try { parsed = JSON.parse(text); if (parsed && (parsed.responseStatus === 'ERROR' || parsed.responseStatus === 'FORBIDDEN')) { const isNotFound = (parsed.message || '').toLowerCase().includes('not found') || parsed.messageCode === 2; return { success: false, requestUrl: fullUrl, usedPath: path, isFileNotFound: isNotFound, errorDetails: `שרת ימות המשיח החזיר: ${parsed.message || 'שגיאה'} (קוד שגיאה: ${parsed.messageCode || 'לא צוין'})` }; } } catch (e) {} let rows = []; if (parsed) { rows = extractRowsFromJson(parsed); } else { rows = extractRowsFromCsv(text); } return { success: rows.length > 0, rows: rows, requestUrl: fullUrl, usedPath: path }; } catch (err) { return { success: false, requestUrl: fullUrl, usedPath: path, errorDetails: `שגיאת תקשורת: ${err.message}` }; } } function extractRowsFromJson(json) { if (Array.isArray(json)) return json; if (json.data && Array.isArray(json.data)) return json.data; if (json.rows && Array.isArray(json.rows)) { const headers = json.headers || []; return json.rows.map(r => { let obj = {}; headers.forEach((h, idx) => { obj[h] = r[idx] !== undefined ? r[idx] : ''; }); return obj; }); } return [json]; } function extractRowsFromCsv(text) { const clean = text.trim(); if (!clean) return []; const rows = parseCSV(clean); if (rows.length < 2) return []; const headers = rows[0]; const data = []; for (let i = 1; i < rows.length; i++) { const r = rows[i]; if (r.length === 0 || (r.length === 1 && r[0] === '')) continue; let item = {}; headers.forEach((h, idx) => { item[h] = r[idx] !== undefined ? r[idx] : ''; }); data.push(item); } return data; } function parseCSV(text) { const firstLine = text.split(/\r\n|\n/)[0] || ''; const delimiter = firstLine.includes('\t') && !firstLine.includes(',') ? '\t' : ','; const rows = []; let currentRow = []; let currentVal = ''; let inQuotes = false; for (let i = 0; i < text.length; i++) { const c = text[i]; const next = text[i + 1]; if (c === '"') { if (inQuotes && next === '"') { currentVal += '"'; i++; } else { inQuotes = !inQuotes; } } else if (c === delimiter && !inQuotes) { currentRow.push(currentVal.trim()); currentVal = ''; } else if ((c === '\r' || c === '\n') && !inQuotes) { if (c === '\r' && next === '\n') i++; currentRow.push(currentVal.trim()); if (currentRow.some(col => col.length > 0)) rows.push(currentRow); currentRow = []; currentVal = ''; } else { currentVal += c; } } if (currentVal || currentRow.length > 0) { currentRow.push(currentVal.trim()); if (currentRow.some(col => col.length > 0)) rows.push(currentRow); } return rows; } async function handleLocalFile(e) { hideBanner(); const file = e.target.files[0]; if (!file) return; const content = await file.text(); let rows = []; try { if (file.name.endsWith('.json')) { rows = extractRowsFromJson(JSON.parse(content)); } else { rows = extractRowsFromCsv(content); } if (rows.length > 0) { loadData(rows, file.name); showBanner(`הקובץ "${file.name}" נטען בהצלחה! סה"כ ${rows.length.toLocaleString('he-IL')} עסקאות.`, 'success'); } else { showBanner('לא זוהו עסקאות בקובץ זה.', 'warning'); } } catch (err) { showBanner('שגיאה בקריאת הקובץ: ' + err.message, 'danger'); } } function resolveExtensionName(row, pathUsed) { let rawExt = ''; const extKeys = ['שלוחה', 'תיקיה', 'תיקייה', 'extension', 'Folder', 'folder', 'ApiExtension']; for (let k of extKeys) { if (row[k] !== undefined && String(row[k]).trim() !== '') { rawExt = String(row[k]).trim(); break; } } if (!rawExt && pathUsed) { const match = pathUsed.match(/ivr2:?\/?([^/]+)\/LogCreditCardOK\.ymgr/i); if (match && match[1]) { rawExt = match[1]; } else if (pathUsed.includes('LogCreditCardOK.ymgr')) { rawExt = 'ראשי'; } } if (!rawExt || rawExt === 'Log' || rawExt === '/') rawExt = 'ראשי'; if (appConfig.extensionAliases) { const pairs = appConfig.extensionAliases.split(/[,;\n]/); for (let p of pairs) { const [key, val] = p.split(':'); if (key && val && key.trim() === rawExt) { return val.trim(); } } } return rawExt === 'ראשי' ? 'שלוחה ראשית' : `שלוחה ${rawExt}`; } function applyExtensionNamesToData(pathUsed) { rawData.forEach(row => { row['תשלום ל...'] = resolveExtensionName(row, pathUsed); }); } function getRowDateTimeTimestamp(row) { let dVal = autoDateCol ? row[autoDateCol] : null; let tVal = autoTimeCol ? row[autoTimeCol] : null; if (!tVal) { const timeCandidates = ['שעה', 'time', 'Time']; for (let tc of timeCandidates) { if (row[tc]) { tVal = row[tc]; break; } } } if (dVal) { let combined = String(dVal).trim(); if (tVal) combined += ' ' + String(tVal).trim(); let parsed = parseDateValue(combined); if (parsed) return parsed.getTime(); } return null; } // טעינת הנתונים, סינון גורף של השדות המבוקשים והוספת "תשלום ל..." בראש function loadData(data, pathUsed) { rawData = data.map(origRow => { const cleanedRow = {}; cleanedRow['תשלום ל...'] = resolveExtensionName(origRow, pathUsed); // העתקה אך ורק של שדות שאינם ברשימת ההסרה Object.keys(origRow).forEach(key => { if (!isFieldExcluded(key) && key !== 'תשלום ל...') { cleanedRow[key] = origRow[key]; } }); return cleanedRow; }); if (rawData.length === 0) { allColumns = []; visibleColumns = []; applyFilters(); return; } // הפיכת סדר הרשומות כדי שהחדשות בתחתית הקובץ יוצגו ראשונות rawData.reverse(); // בניית רשימת העמודות הסופית (תשלום ל... תמיד ראשון) const colSet = new Set(['תשלום ל...']); rawData.forEach(item => { Object.keys(item).forEach(k => { if (!isFieldExcluded(k)) { colSet.add(k); } }); }); allColumns = Array.from(colSet); visibleColumns = [...allColumns]; detectSpecialColumns(); // ברירת מחדל: מהחדש לישן currentSort = { col: autoDateCol, asc: false }; populateColumnSelectors(); applyFilters(); } function detectSpecialColumns() { const dateCandidates = ['תאריך', 'date', 'Date', 'DateTime', 'התחלה', 'ApiTime']; autoDateCol = null; for (let c of dateCandidates) { const match = allColumns.find(col => col.toLowerCase().includes(c.toLowerCase())); if (match) { autoDateCol = match; break; } } const timeCandidates = ['שעה', 'time', 'Time']; autoTimeCol = null; for (let c of timeCandidates) { const match = allColumns.find(col => col.toLowerCase() === c.toLowerCase()); if (match) { autoTimeCol = match; break; } } const amountCandidates = ['סכום', 'amount', 'sum', 'billing_sum', 'סה"כ', 'מחיר']; amountCol = null; for (let c of amountCandidates) { const match = allColumns.find(col => col.toLowerCase().includes(c.toLowerCase())); if (match) { amountCol = match; break; } } } function populateColumnSelectors() { const dateSelect = document.getElementById('dateColumnSelect'); dateSelect.innerHTML = `<option value="">(אוטומטי: ${autoDateCol || 'לא זוהה'})</option>`; allColumns.forEach(c => { dateSelect.innerHTML += `<option value="${c}">${c}</option>`; }); const specificSelect = document.getElementById('specificColSelect'); specificSelect.innerHTML = `<option value="">כל העמודות</option>`; allColumns.forEach(c => { specificSelect.innerHTML += `<option value="${c}">${c}</option>`; }); } function parseDateValue(val) { if (!val) return null; val = String(val).trim(); const dmy = val.match(/^(\d{1,2})[\/\.-](\d{1,2})[\/\.-](\d{4})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/); if (dmy) { return new Date( parseInt(dmy[3], 10), parseInt(dmy[2], 10) - 1, parseInt(dmy[1], 10), parseInt(dmy[4] || 0, 10), parseInt(dmy[5] || 0, 10), parseInt(dmy[6] || 0, 10) ); } const ymd = val.match(/^(\d{4})[\/\.-](\d{1,2})[\/\.-](\d{1,2})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/); if (ymd) { return new Date( parseInt(ymd[1], 10), parseInt(ymd[2], 10) - 1, parseInt(ymd[3], 10), parseInt(ymd[4] || 0, 10), parseInt(ymd[5] || 0, 10), parseInt(ymd[6] || 0, 10) ); } if (/^\d{10}$/.test(val)) return new Date(parseInt(val, 10) * 1000); if (/^\d{13}$/.test(val)) return new Date(parseInt(val, 10)); const standard = new Date(val); if (!isNaN(standard.getTime())) return standard; return null; } function applyFilters() { const globalTerm = document.getElementById('globalSearch').value.trim().toLowerCase(); const specCol = document.getElementById('specificColSelect').value; const specVal = document.getElementById('specificColValue').value.trim().toLowerCase(); const chosenDateCol = document.getElementById('dateColumnSelect').value || autoDateCol; const dateFromVal = document.getElementById('dateFrom').value; const dateToVal = document.getElementById('dateTo').value; const dateFrom = dateFromVal ? new Date(dateFromVal + 'T00:00:00') : null; const dateTo = dateToVal ? new Date(dateToVal + 'T23:59:59') : null; filteredData = rawData.filter(row => { if (chosenDateCol && (dateFrom || dateTo)) { const parsed = parseDateValue(row[chosenDateCol]); if (parsed) { if (dateFrom && parsed < dateFrom) return false; if (dateTo && parsed > dateTo) return false; } } if (globalTerm) { const matchGlobal = Object.values(row).some(v => String(v).toLowerCase().includes(globalTerm)); if (!matchGlobal) return false; } if (specCol && specVal) { const val = String(row[specCol] || '').toLowerCase(); if (!val.includes(specVal)) return false; } return true; }); // מיון: שילוב תאריך + שעה if (currentSort.col) { filteredData.sort((a, b) => { if (currentSort.col === autoDateCol) { const dtA = getRowDateTimeTimestamp(a); const dtB = getRowDateTimeTimestamp(b); if (dtA !== null && dtB !== null) { return currentSort.asc ? dtA - dtB : dtB - dtA; } } let va = a[currentSort.col] || ''; let vb = b[currentSort.col] || ''; const na = parseFloat(va); const nb = parseFloat(vb); if (!isNaN(na) && !isNaN(nb)) return currentSort.asc ? na - nb : nb - na; const da = parseDateValue(va); const db = parseDateValue(vb); if (da && db) return currentSort.asc ? da - db : db - da; return currentSort.asc ? String(va).localeCompare(String(vb), 'he') : String(vb).localeCompare(String(va), 'he'); }); } currentPage = 1; updateStats(); renderTable(); } function updateStats() { document.getElementById('statTotalRows').textContent = rawData.length.toLocaleString('he-IL'); document.getElementById('statFilteredRows').textContent = filteredData.length.toLocaleString('he-IL'); if (amountCol) { const totalSum = filteredData.reduce((acc, row) => { const raw = String(row[amountCol] || '').replace(/[^\d.-]/g, ''); const val = parseFloat(raw); return acc + (isNaN(val) ? 0 : val); }, 0); document.getElementById('statTotalSum').textContent = `₪${totalSum.toLocaleString('he-IL', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } else { document.getElementById('statTotalSum').textContent = '-'; } const phoneCandidates = ['טלפון', 'phone', 'Phone', 'callerId', 'callerIdNum', 'מספר מחייג']; let phoneCol = allColumns.find(c => phoneCandidates.some(pc => c.toLowerCase().includes(pc.toLowerCase()))); if (phoneCol) { const phones = new Set(filteredData.map(r => r[phoneCol]).filter(Boolean)); document.getElementById('statUniquePhones').textContent = phones.size.toLocaleString('he-IL'); } else { document.getElementById('statUniquePhones').textContent = '-'; } } function setQuickDate(type) { document.querySelectorAll('.quick-dates .chip-btn').forEach(b => b.classList.remove('active')); event.target.classList.add('active'); const now = new Date(); const dateFrom = document.getElementById('dateFrom'); const dateTo = document.getElementById('dateTo'); function formatYMD(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } if (type === 'today') { dateFrom.value = formatYMD(now); dateTo.value = formatYMD(now); } else if (type === 'yesterday') { const y = new Date(); y.setDate(now.getDate() - 1); dateFrom.value = formatYMD(y); dateTo.value = formatYMD(y); } else if (type === '7days') { const prev = new Date(); prev.setDate(now.getDate() - 6); dateFrom.value = formatYMD(prev); dateTo.value = formatYMD(now); } else if (type === '30days') { const prev = new Date(); prev.setDate(now.getDate() - 29); dateFrom.value = formatYMD(prev); dateTo.value = formatYMD(now); } else if (type === 'month') { const first = new Date(now.getFullYear(), now.getMonth(), 1); dateFrom.value = formatYMD(first); dateTo.value = formatYMD(now); } else if (type === 'all') { dateFrom.value = ''; dateTo.value = ''; } applyFilters(); } function resetFilters() { document.getElementById('globalSearch').value = ''; document.getElementById('specificColSelect').value = ''; document.getElementById('specificColValue').value = ''; document.getElementById('dateFrom').value = ''; document.getElementById('dateTo').value = ''; document.querySelectorAll('.quick-dates .chip-btn').forEach(b => b.classList.remove('active')); currentSort = { col: autoDateCol, asc: false }; applyFilters(); } function renderTable() { const thead = document.getElementById('tableHead'); const tbody = document.getElementById('tableBody'); if (filteredData.length === 0) { thead.innerHTML = `<tr><th>#</th><th>תוצאה</th></tr>`; tbody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 2.5rem; color: var(--text-muted);">לא נמצאו עסקאות התואמות את הסינון</td></tr>`; renderPagination(); return; } let headHtml = `<tr><th style="width: 50px; text-align: center;">#</th>`; visibleColumns.forEach(c => { let isSorted = currentSort.col === c; let sortIcon = '↕'; if (isSorted) { sortIcon = currentSort.asc ? '▲ (מהישן לחדש)' : '▼ (מהחדש לישן)'; } headHtml += `<th class="${isSorted ? 'sorted-col' : ''}" onclick="sortTable('${c}')" title="לחץ לשינוי סדר המיון"> <span class="sort-icon">${sortIcon}</span> ${c} </th>`; }); headHtml += `</tr>`; thead.innerHTML = headHtml; let start = (currentPage - 1) * pageSize; let end = pageSize === 'all' ? filteredData.length : start + pageSize; if (pageSize === 'all') start = 0; const pageRows = filteredData.slice(start, end); const decodeYemot = document.getElementById('decodeYemotCharsCheck').checked; let bodyHtml = ''; pageRows.forEach((row, idx) => { const rowNum = start + idx + 1; bodyHtml += `<tr onclick="openModal(${rowNum - 1})"><td style="text-align: center; font-weight: 600; color: var(--text-muted);">${rowNum}</td>`; visibleColumns.forEach(c => { let val = row[c] !== undefined ? String(row[c]) : ''; if (decodeYemot && (val.includes('^') || val.includes('*'))) { val = val.replace(/\^/g, '=').replace(/\*/g, '&'); } if (c === 'תשלום ל...') { bodyHtml += `<td><span class="badge-payment-for">${escapeHtml(val)}</span></td>`; return; } if (c === amountCol && !isNaN(parseFloat(val))) { val = `₪${parseFloat(val).toLocaleString('he-IL', { minimumFractionDigits: 2 })}`; bodyHtml += `<td style="font-weight: 700; color: #059669;">${val}</td>`; return; } bodyHtml += `<td title="${escapeHtml(val)}">${escapeHtml(val)}</td>`; }); bodyHtml += `</tr>`; }); tbody.innerHTML = bodyHtml; renderPagination(); } function escapeHtml(str) { return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } function sortTable(col) { if (currentSort.col === col) { currentSort.asc = !currentSort.asc; } else { currentSort.col = col; currentSort.asc = (col === autoDateCol) ? false : true; } applyFilters(); } function changePageSize() { const val = document.getElementById('pageSizeSelect').value; pageSize = val === 'all' ? 'all' : parseInt(val, 10); currentPage = 1; renderTable(); } function renderPagination() { const total = filteredData.length; const nav = document.getElementById('paginationNav'); const info = document.getElementById('paginationInfo'); if (total === 0 || pageSize === 'all') { info.textContent = `מציג ${total.toLocaleString('he-IL')} מתוך ${total.toLocaleString('he-IL')} עסקאות`; nav.innerHTML = ''; return; } const totalPages = Math.ceil(total / pageSize); const start = (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, total); info.textContent = `מציג ${start.toLocaleString('he-IL')}-${end.toLocaleString('he-IL')} מתוך ${total.toLocaleString('he-IL')} עסקאות`; let navHtml = ''; navHtml += `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="goToPage(1)">«</button>`; navHtml += `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="goToPage(${currentPage - 1})">‹</button>`; let pStart = Math.max(1, currentPage - 2); let pEnd = Math.min(totalPages, currentPage + 2); for (let p = pStart; p <= pEnd; p++) { navHtml += `<button class="page-btn ${p === currentPage ? 'active' : ''}" onclick="goToPage(${p})">${p}</button>`; } navHtml += `<button class="page-btn" ${currentPage === totalPages ? 'disabled' : ''} onclick="goToPage(${currentPage + 1})">›</button>`; navHtml += `<button class="page-btn" ${currentPage === totalPages ? 'disabled' : ''} onclick="goToPage(${totalPages})">»</button>`; nav.innerHTML = navHtml; } function goToPage(p) { currentPage = p; renderTable(); } function openModal(indexInFiltered) { const row = filteredData[indexInFiltered]; if (!row) return; document.getElementById('modalTitle').textContent = `פירוט עסקה #${indexInFiltered + 1}`; const content = document.getElementById('modalContent'); content.innerHTML = ''; const decodeYemot = document.getElementById('decodeYemotCharsCheck').checked; visibleColumns.forEach(c => { let val = row[c] !== undefined ? String(row[c]) : ''; let decodedVal = ''; if (decodeYemot && (val.includes('^') || val.includes('*'))) { decodedVal = val.replace(/\^/g, '=').replace(/\*/g, '&'); } const div = document.createElement('div'); div.style.cssText = 'display: grid; grid-template-columns: 140px 1fr; padding: 0.55rem 0; border-bottom: 1px solid #f1f5f9; gap: 0.5rem; font-size: 0.9rem;'; div.innerHTML = ` <div style="font-weight: 600; color: var(--text-muted);">${escapeHtml(c)}</div> <div style="word-break: break-all;"> <span style="${c === amountCol ? 'font-weight: 700; color: #059669;' : ''}">${escapeHtml(val || '-')}</span> ${decodedVal ? `<div style="margin-top: 4px; color: var(--primary); font-size: 0.82rem;"><strong>מפוענח:</strong> ${escapeHtml(decodedVal)}</div>` : ''} </div> `; content.appendChild(div); }); document.getElementById('detailModal').classList.add('active'); } function closeModal(e) { if (!e || e.target.classList.contains('modal-overlay') || e.target.classList.contains('close-btn')) { document.getElementById('detailModal').classList.remove('active'); } } function exportData(type) { if (filteredData.length === 0) { alert('אין נתונים מסוננים לייצוא.'); return; } if (type === 'json') { const jsonStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(filteredData, null, 2)); downloadFile(jsonStr, "LogCreditCardOK_Clean.json"); } else if (type === 'csv') { let csvContent = "\uFEFF"; csvContent += visibleColumns.map(c => `"${c.replace(/"/g, '""')}"`).join(",") + "\r\n"; filteredData.forEach(row => { let line = visibleColumns.map(c => { let v = row[c] !== undefined ? String(row[c]) : ""; return `"${v.replace(/"/g, '""')}"`; }).join(","); csvContent += line + "\r\n"; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); downloadFile(url, "LogCreditCardOK_Clean.csv"); } } function downloadFile(url, filename) { const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); } </script> </body> </html> -
@החברמן
אתה רוצה תוכנה?
יש לי כזו ממש טרייה -
@אA נו אפשר?
תודה -
@החברמן אתה יכול לעשות אקסל שיקח את הנתונים מהקובץ ymgr.
-
תוכנה לדוחות סליקה
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>דוח עסקאות אשראי - LogCreditCardOK | ימות המשיח</title> <style> :root { --primary: #2563eb; --primary-hover: #1d4ed8; --primary-light: #eff6ff; --success: #10b981; --warning: #f59e0b; --danger: #ef4444; --bg: #f8fafc; --card-bg: #ffffff; --text: #0f172a; --text-muted: #64748b; --border: #e2e8f0; --radius: 12px; --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.07); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); } * { box-sizing: border-box; margin: 0; padding: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Heebo', Arial, sans-serif; } body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; } header { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); color: #fff; padding: 0.85rem 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .brand { display: flex; align-items: center; gap: 0.75rem; } .brand-icon { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 1.3rem; box-shadow: 0 2px 8px rgba(16,185,129,0.3); } .brand-title h1 { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.2px; } .brand-title p { font-size: 0.75rem; color: #94a3b8; font-family: monospace; } .header-actions { display: flex; align-items: center; gap: 0.75rem; } .settings-btn-pill { display: inline-flex; align-items: center; gap: 0.6rem; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.18); color: #f8fafc; padding: 0.5rem 1.1rem; border-radius: 30px; cursor: pointer; font-size: 0.88rem; font-weight: 600; backdrop-filter: blur(8px); transition: all 0.2s ease; box-shadow: 0 2px 6px rgba(0,0,0,0.12); } .settings-btn-pill:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.35); transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,0.2); } .settings-btn-pill .gear-icon { font-size: 1.1rem; } .status-indicator-dot { width: 8px; height: 8px; border-radius: 50%; background: #f59e0b; box-shadow: 0 0 6px #f59e0b; transition: background 0.3s; } .status-indicator-dot.active { background: #10b981; box-shadow: 0 0 8px #10b981; } .container { max-width: 1440px; width: 100%; margin: 0 auto; padding: 1.5rem; flex: 1; } .card { background: var(--card-bg); border-radius: var(--radius); border: 1px solid var(--border); box-shadow: var(--shadow); padding: 1.25rem; margin-bottom: 1.5rem; } .clean-action-bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; } .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 0.65rem 1.25rem; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; transition: all 0.2s; font-size: 0.95rem; white-space: nowrap; } .btn-primary { background: var(--primary); color: #fff; } .btn-primary:hover { background: var(--primary-hover); } .btn-success { background: var(--success); color: #fff; } .btn-success:hover { background: #059669; } .btn-secondary { background: #f1f5f9; color: #334155; border: 1px solid var(--border); } .btn-secondary:hover { background: #e2e8f0; } .form-control { padding: 0.65rem 0.85rem; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; background-color: #fff; transition: border-color 0.2s; } .form-control:focus { outline: none; border-color: var(--primary); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; } .stat-card { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.1rem 1.25rem; display: flex; flex-direction: column; gap: 0.25rem; box-shadow: var(--shadow); } .stat-label { font-size: 0.8rem; color: var(--text-muted); font-weight: 500; } .stat-value { font-size: 1.6rem; font-weight: 800; color: var(--text); } .stat-value.amount { color: #059669; } .filter-panel { background: #fff; border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem; margin-bottom: 1.5rem; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 1rem; } .filter-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; } .quick-dates { display: flex; flex-wrap: wrap; gap: 0.4rem; } .chip-btn { border: 1px solid var(--border); background: #fff; padding: 0.35rem 0.75rem; border-radius: 20px; font-size: 0.8rem; font-weight: 500; cursor: pointer; transition: all 0.15s; } .chip-btn:hover { background: var(--primary-light); border-color: var(--primary); color: var(--primary); } .chip-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; } .sort-indicator-badge { display: inline-flex; align-items: center; gap: 0.3rem; background: #f1f5f9; color: #475569; padding: 0.25rem 0.6rem; border-radius: 6px; font-size: 0.8rem; font-weight: 600; } .table-responsive { overflow-x: auto; background: #fff; border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); position: relative; max-height: 65vh; } table { width: 100%; border-collapse: collapse; text-align: right; font-size: 0.9rem; } thead { position: sticky; top: 0; background: #f8fafc; z-index: 10; border-bottom: 2px solid var(--border); } th { padding: 0.85rem 1rem; font-weight: 700; color: #334155; user-select: none; cursor: pointer; white-space: nowrap; } th:hover { background: #f1f5f9; } th .sort-icon { font-size: 0.75rem; margin-right: 0.35rem; color: #94a3b8; font-weight: 800; } th.sorted-col .sort-icon { color: var(--primary); } td { padding: 0.75rem 1rem; border-bottom: 1px solid #f1f5f9; white-space: nowrap; max-width: 300px; overflow: hidden; text-overflow: ellipsis; } tr:hover td { background-color: #f8fafc; } tr { cursor: pointer; } .badge-payment-for { background: #e0f2fe; color: #0369a1; padding: 0.25rem 0.65rem; border-radius: 6px; font-weight: 700; font-size: 0.85rem; display: inline-block; border: 1px solid #bae6fd; } .pagination-bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-top: 1rem; padding: 0.5rem 0; } .page-nav { display: flex; align-items: center; gap: 0.3rem; } .page-btn { min-width: 34px; height: 34px; padding: 0 0.5rem; border: 1px solid var(--border); background: #fff; border-radius: 6px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-weight: 500; font-size: 0.85rem; } .page-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); } .page-btn:disabled { opacity: 0.4; cursor: not-allowed; } .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 23, 42, 0.65); backdrop-filter: blur(4px); display: none; align-items: center; justify-content: center; z-index: 1000; padding: 1rem; } .modal-overlay.active { display: flex; } .modal { background: #fff; border-radius: var(--radius); width: 100%; max-width: 620px; max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lg); display: flex; flex-direction: column; } .modal-header { padding: 1.25rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; } .modal-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; } .close-btn { background: transparent; border: none; font-size: 1.5rem; cursor: pointer; color: var(--text-muted); } .banner { padding: 0.85rem 1.1rem; border-radius: 8px; margin-bottom: 1rem; font-size: 0.9rem; display: none; line-height: 1.5; } .banner-danger { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; } .banner-success { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; } .banner-warning { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; } .modal-quick-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.4rem; } .modal-chip { background: #f1f5f9; border: 1px solid var(--border); padding: 0.25rem 0.55rem; border-radius: 6px; font-size: 0.78rem; cursor: pointer; font-family: monospace; direction: ltr; } .modal-chip:hover { background: var(--primary-light); border-color: var(--primary); color: var(--primary); } .spinner { border: 2px solid #f3f3f3; border-top: 2px solid #fff; border-radius: 50%; width: 16px; height: 16px; animation: spin 0.8s linear infinite; display: inline-block; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> </head> <body> <!-- כותרת ראשית --> <header> <div class="brand"> <div class="brand-icon">💳</div> <div class="brand-title"> <h1>דוח עסקאות אשראי - LogCreditCardOK</h1> <p>שליפת קובץ YMGR ישירות בפקודת RenderYMGRFile</p> </div> </div> <!-- כפתור הגדרות מעוצב עם סטטוס חיבור --> <div class="header-actions"> <button class="settings-btn-pill" onclick="openConfigModal()" title="לחץ להגדרת נתיב, קובץ ופרטי API"> <span class="status-indicator-dot" id="headerStatusDot"></span> <span class="gear-icon">⚙️</span> <span id="headerStatusText">הגדרות API</span> </button> </div> </header> <div class="container"> <div id="bannerBox" class="banner"></div> <!-- דשבורד ראשי נקי (ללא שדות נתיב וקבצים) --> <div class="card"> <div class="clean-action-bar"> <div> <h2 style="font-size: 1.15rem; font-weight: 700; margin-bottom: 0.25rem;">ניהול וצפייה בעסקאות אשראי</h2> <p style="font-size: 0.85rem; color: var(--text-muted);"> שליפה ישירה מול המערכת, סינון תאריכים מתקדם וחישוב סכומים אוטומטי. </p> </div> <div style="display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;"> <button class="btn btn-secondary" onclick="document.getElementById('fileUploadInput').click()"> 📁 טען קובץ ידנית </button> <input type="file" id="fileUploadInput" accept=".ymgr,.csv,.json,.txt" style="display: none;" onchange="handleLocalFile(event)"> <button id="fetchBtn" class="btn btn-success" style="padding: 0.75rem 1.6rem; font-size: 1rem;" onclick="fetchCreditCardReport()"> <span>⚡ שלוף עסקאות אשראי</span> </button> </div> </div> </div> <!-- כרטיסי סיכום וסטטיסטיקה --> <div class="stats-grid"> <div class="stat-card"> <span class="stat-label">סה"כ עסקאות</span> <span class="stat-value" id="statTotalRows">0</span> </div> <div class="stat-card"> <span class="stat-label">עסקאות מסוננות</span> <span class="stat-value" id="statFilteredRows" style="color: var(--primary);">0</span> </div> <div class="stat-card"> <span class="stat-label">סה"כ סכום עסקאות (₪)</span> <span class="stat-value amount" id="statTotalSum">₪0.00</span> </div> <div class="stat-card"> <span class="stat-label">תורמים / טלפונים ייחודיים</span> <span class="stat-value" id="statUniquePhones">0</span> </div> </div> <!-- סרגל סינונים וטווח תאריכים --> <div class="filter-panel"> <div class="filter-row"> <div class="form-group" style="min-width: 140px;"> <label for="dateColumnSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">עמודת תאריך</label> <select id="dateColumnSelect" class="form-control" onchange="applyFilters()"> <option value="">(זיהוי אוטומטי)</option> </select> </div> <div class="form-group"> <label for="dateFrom" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">מתאריך</label> <input type="date" id="dateFrom" class="form-control" onchange="applyFilters()"> </div> <div class="form-group"> <label for="dateTo" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">עד תאריך</label> <input type="date" id="dateTo" class="form-control" onchange="applyFilters()"> </div> <div class="form-group" style="flex: 1;"> <label style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">טווחים מהירים</label> <div class="quick-dates"> <button class="chip-btn" onclick="setQuickDate('today')">היום</button> <button class="chip-btn" onclick="setQuickDate('yesterday')">אתמול</button> <button class="chip-btn" onclick="setQuickDate('7days')">7 ימים אחרונים</button> <button class="chip-btn" onclick="setQuickDate('30days')">30 ימים אחרונים</button> <button class="chip-btn" onclick="setQuickDate('month')">החודש הנוכחי</button> <button class="chip-btn" onclick="setQuickDate('all')">הכל</button> </div> </div> </div> <div class="filter-row"> <div class="form-group" style="flex: 2; min-width: 240px;"> <label for="globalSearch" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">חיפוש חופשי (טלפון, 4 ספרות, אישור, שם, שלוחה...)</label> <input type="text" id="globalSearch" class="form-control" placeholder="חפש לפי מספר טלפון, סכום, מספר אישור..." oninput="applyFilters()"> </div> <div class="form-group" style="flex: 1; min-width: 160px;"> <label for="specificColSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">סינון לפי עמודה</label> <select id="specificColSelect" class="form-control" onchange="applyFilters()"> <option value="">כל העמודות</option> </select> </div> <div class="form-group" style="flex: 1; min-width: 160px;"> <label for="specificColValue" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">ערך בעמודה</label> <input type="text" id="specificColValue" class="form-control" placeholder="סנן ערך..." oninput="applyFilters()"> </div> <div class="form-group" style="display: flex; align-items: center; justify-content: flex-end; padding-top: 1.4rem;"> <label style="display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; cursor: pointer;"> <input type="checkbox" id="decodeYemotCharsCheck" checked onchange="renderTable()"> פענוח תווים (^ ו-*) </label> </div> </div> <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.75rem; border-top: 1px solid var(--border); padding-top: 0.75rem;"> <div style="display: flex; gap: 0.5rem; flex-wrap: wrap;"> <button class="btn btn-secondary" onclick="exportData('csv')">📥 ייצוא ל-Excel / CSV</button> <button class="btn btn-secondary" onclick="exportData('json')">📄 ייצוא JSON</button> <button class="btn btn-secondary" onclick="window.print()">🖨️ הדפסה</button> <button class="btn btn-secondary" onclick="resetFilters()">🔄 איפוס סינונים</button> </div> <div style="display: flex; gap: 0.75rem; align-items: center;"> <span class="sort-indicator-badge">סדר תצוגה: מהחדש לישן ▼</span> <select id="pageSizeSelect" class="form-control" style="width: auto;" onchange="changePageSize()"> <option value="15">15 שורות לעמוד</option> <option value="25" selected>25 שורות לעמוד</option> <option value="50">50 שורות לעמוד</option> <option value="100">100 שורות לעמוד</option> <option value="all">כל השורות</option> </select> </div> </div> </div> <!-- טבלת נתונים --> <div class="table-responsive"> <table id="dataTable"> <thead id="tableHead"> <tr> <th style="width: 50px; text-align: center;">#</th> <th>אין נתונים להצגה - לחצו על כפתור השליפה למעלה</th> </tr> </thead> <tbody id="tableBody"> <tr> <td colspan="2" style="text-align: center; padding: 3rem; color: var(--text-muted);"> נא לוודא שהטוקן מוגדר ב-⚙️ הגדרות, וללחוץ על <strong>"⚡ שלוף עסקאות אשראי"</strong> </td> </tr> </tbody> </table> </div> <!-- דפדוף --> <div class="pagination-bar"> <div id="paginationInfo" style="font-size: 0.85rem; color: var(--text-muted);">מציג 0-0 מתוך 0 עסקאות</div> <div class="page-nav" id="paginationNav"></div> </div> </div> <!-- חלון הגדרות API מרוכז (כולל שינוי נתיב ושם קובץ) --> <div class="modal-overlay" id="configModal" onclick="closeConfigModal(event)"> <div class="modal" onclick="event.stopPropagation()"> <div class="modal-header"> <h3 style="display: flex; align-items: center; gap: 0.5rem;"> <span>⚙️</span> הגדרות API ונתיבי קבצים </h3> <button class="close-btn" onclick="closeConfigModal()">×</button> </div> <div class="modal-body"> <div class="form-group"> <label for="cfgServerSelect" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">שרת API</label> <select id="cfgServerSelect" class="form-control" onchange="toggleCustomServerInput()"> <option value="https://www.call2all.co.il/ym/api/">שרת ראשי (www.call2all.co.il)</option> <option value="https://private.call2all.co.il/ym/api/">שרת פרטי (private.call2all.co.il)</option> <option value="custom">כתובת מותאמת אישית...</option> </select> </div> <div class="form-group" id="cfgCustomServerWrap" style="display: none;"> <label for="cfgCustomServerUrl" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">כתובת שרת מותאמת</label> <input type="text" id="cfgCustomServerUrl" class="form-control" placeholder="https://your-server.com/api/"> </div> <div class="form-group"> <label for="cfgTokenInput" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;">טוקן הזדהות / מספר מערכת וסיסמה (token)</label> <input type="password" id="cfgTokenInput" class="form-control" placeholder="למשל: 0771234567:1234 או API Key"> <small style="color: var(--text-muted); font-size: 0.78rem;">דוגמה: 0771234567:1234 או מפתח API מחומת האש.</small> </div> <!-- הגדרת נתיב ושם קובץ ה-YMGR רק כאן בהגדרות --> <div class="form-group" style="border-top: 1px solid var(--border); padding-top: 0.85rem;"> <label for="cfgPathInput" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;"> נתיב ושם קובץ ה-YMGR לשליפה (פרמטר wath): </label> <input type="text" id="cfgPathInput" class="form-control" value="ivr2:/Log/LogCreditCardOK.ymgr" style="direction: ltr; font-family: monospace;"> <div class="modal-quick-chips"> <span style="font-size: 0.78rem; color: var(--text-muted); align-self: center;">קיצורים לבחירה:</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/Log/LogCreditCardOK.ymgr'">ivr2:/Log/LogCreditCardOK.ymgr (ראשי)</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/LogCreditCardOK.ymgr'">ivr2:/LogCreditCardOK.ymgr (שורש)</span> <span class="modal-chip" onclick="document.getElementById('cfgPathInput').value='ivr2:/1/LogCreditCardOK.ymgr'">ivr2:/1/LogCreditCardOK.ymgr (שלוחה 1)</span> </div> </div> <!-- שמות שלוחות ידידותיים --> <div class="form-group" style="border-top: 1px solid var(--border); padding-top: 0.85rem;"> <label for="cfgExtensionAliases" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); display: block; margin-bottom: 0.25rem;"> שמות מותאמים לעמודת "תשלום ל..." (אופציונלי): </label> <input type="text" id="cfgExtensionAliases" class="form-control" placeholder="לדוגמה: 1:תרומות כללי, 2:קרן עזרה, ראשי:קופה ראשית"> <small style="color: var(--text-muted); font-size: 0.78rem;">פורמט: מספר_שלוחה:שם_השלוחה, מופרד בפסיקים.</small> </div> <div style="display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 0.75rem;"> <button class="btn btn-secondary" onclick="closeConfigModal()">סגור</button> <button class="btn btn-primary" onclick="saveConfiguration()">שמור הגדרות</button> </div> </div> </div> </div> <!-- חלונית פירוט רשומה --> <div class="modal-overlay" id="detailModal" onclick="closeModal(event)"> <div class="modal" onclick="event.stopPropagation()"> <div class="modal-header"> <h3 id="modalTitle">פרטי עסקה</h3> <button class="close-btn" onclick="closeModal()">×</button> </div> <div class="modal-body" id="modalContent"></div> </div> </div> <script> let appConfig = { server: 'https://www.call2all.co.il/ym/api/', customServer: '', token: '', filePath: 'ivr2:/Log/LogCreditCardOK.ymgr', extensionAliases: '' }; let rawData = []; let filteredData = []; let allColumns = []; let visibleColumns = []; let currentSort = { col: null, asc: false }; // ברירת מחדל: מהחדש לישן let currentPage = 1; let pageSize = 25; let autoDateCol = null; let autoTimeCol = null; let amountCol = null; window.addEventListener('DOMContentLoaded', () => { loadAppConfig(); }); function loadAppConfig() { try { const saved = localStorage.getItem('ym_credit_card_cfg_direct_v7'); if (saved) { appConfig = Object.assign(appConfig, JSON.parse(saved)); } } catch (e) {} updateConnectionStatusBadge(); } function updateConnectionStatusBadge() { const dot = document.getElementById('headerStatusDot'); const txt = document.getElementById('headerStatusText'); if (appConfig.token && appConfig.token.trim()) { dot.className = 'status-indicator-dot active'; let preview = appConfig.token.includes(':') ? appConfig.token.split(':')[0] : 'מוגדר'; txt.textContent = `מחובר (${preview})`; } else { dot.className = 'status-indicator-dot'; txt.textContent = 'הגדרות API'; } } function saveConfigQuietly() { try { localStorage.setItem('ym_credit_card_cfg_direct_v7', JSON.stringify(appConfig)); } catch (e) {} } function openConfigModal() { document.getElementById('cfgServerSelect').value = appConfig.server; document.getElementById('cfgCustomServerUrl').value = appConfig.customServer || ''; document.getElementById('cfgTokenInput').value = appConfig.token || ''; document.getElementById('cfgPathInput').value = appConfig.filePath || 'ivr2:/Log/LogCreditCardOK.ymgr'; document.getElementById('cfgExtensionAliases').value = appConfig.extensionAliases || ''; toggleCustomServerInput(); document.getElementById('configModal').classList.add('active'); } function closeConfigModal(e) { if (!e || e.target.classList.contains('modal-overlay') || e.target.classList.contains('close-btn') || e.target.tagName === 'BUTTON') { document.getElementById('configModal').classList.remove('active'); } } function toggleCustomServerInput() { const val = document.getElementById('cfgServerSelect').value; document.getElementById('cfgCustomServerWrap').style.display = val === 'custom' ? 'block' : 'none'; } function saveConfiguration() { appConfig.server = document.getElementById('cfgServerSelect').value; appConfig.customServer = document.getElementById('cfgCustomServerUrl').value.trim(); appConfig.token = document.getElementById('cfgTokenInput').value.trim(); appConfig.filePath = document.getElementById('cfgPathInput').value.trim() || 'ivr2:/Log/LogCreditCardOK.ymgr'; appConfig.extensionAliases = document.getElementById('cfgExtensionAliases').value.trim(); saveConfigQuietly(); updateConnectionStatusBadge(); closeConfigModal(); showBanner('ההגדרות והנתיב נשמרו בהצלחה!', 'success'); if (rawData.length > 0) { applyExtensionNamesToData(appConfig.filePath); applyFilters(); } } function showBanner(text, type = 'danger') { const b = document.getElementById('bannerBox'); b.className = `banner banner-${type}`; b.style.display = 'block'; b.innerHTML = text; b.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function hideBanner() { document.getElementById('bannerBox').style.display = 'none'; } // מנגנון סינון גורף ומוחלט של כל העמודות שביקשת להסיר function isFieldExcluded(fieldName) { if (!fieldName) return false; const clean = fieldName.trim().toLowerCase().replace(/[\u200e\u200f\ufeff"']/g, ''); // 1. מערכת, מספר מערכת, DID if (clean === 'מערכת' || clean.includes('מספר מערכת') || clean === 'did' || clean === 'apidid') return true; // 2. מספר מערכת אליו חייגו, RealDID, מספר מחויג if (clean.includes('חייגו') || clean.includes('מחויג') || clean.includes('מחוייג') || clean.includes('realdid') || clean.includes('מספר משנה')) return true; // 3. חברת סליקה, סוג סליקה, חברה סולקת if (clean.includes('סליקה') || clean.includes('סולקת') || clean === 'חברה' || clean === 'ספק' || clean.includes('credit_card_type')) return true; // 4. מסוף, טרמינל if (clean.includes('מסוף') || clean.includes('terminal')) return true; // 5. מטבע, סוג מטבע if (clean.includes('מטבע') || clean.includes('currency')) return true; // 6. cvv, קוד אבטחה if (clean.includes('cvv') || clean.includes('אבטחה')) return true; // 7. הערות, הערה if (clean.includes('הערה') || clean.includes('הערות') || clean.includes('comment') || clean.includes('remark') || clean.includes('note')) return true; // 8. תוקף כרטיס, תוקף if (clean.includes('תוקף') || clean.includes('expdate') || clean.includes('expiration')) return true; // 9. סוג זיהוי if (clean.includes('סוג זיהוי') || clean.includes('סוג התחברות') || clean.includes('enteridtype')) return true; // 10. מספר זיהוי, תעודת זהות, ת.ז if (clean.includes('מספר זיהוי') || clean.includes('תעודת זהות') || clean.includes('ת.ז') || clean === 'enterid' || clean === 'id') return true; // שלוחה מקורית (מוחלפת על ידי עמודת "תשלום ל...") if (clean === 'שלוחה' || clean === 'תיקיה' || clean === 'תיקייה' || clean === 'apiextension' || clean === 'folder') return true; return false; } // שליפה ישירה מ-API של ימות המשיח async function fetchCreditCardReport() { hideBanner(); if (!appConfig.token || !appConfig.token.trim()) { showBanner('נא להגדיר תחילה את הטוקן בלחיצה על כפתור <strong>⚙️ הגדרות API</strong> בראש הדף.', 'warning'); openConfigModal(); return; } const baseUrl = appConfig.server === 'custom' ? appConfig.customServer : appConfig.server; const cleanBase = baseUrl.endsWith('/') ? baseUrl : baseUrl + '/'; const endpoint = `${cleanBase}RenderYMGRFile`; const btn = document.getElementById('fetchBtn'); const origText = btn.innerHTML; btn.innerHTML = `<span class="spinner"></span> שולף עסקאות...`; btn.disabled = true; const chosenPath = appConfig.filePath || 'ivr2:/Log/LogCreditCardOK.ymgr'; let result = await callDirectApi(endpoint, chosenPath); // ניסיון חלופי אם הקובץ לא נמצא בנתיב המוגדר if (!result.success && result.isFileNotFound) { let altPath = chosenPath.includes('/Log/') ? chosenPath.replace('/Log/', '/') : chosenPath.replace('ivr2:/', 'ivr2:/Log/'); btn.innerHTML = `<span class="spinner"></span> בודק נתיב חלופי...`; let altResult = await callDirectApi(endpoint, altPath); if (altResult.success) { result = altResult; appConfig.filePath = altPath; saveConfigQuietly(); } } btn.innerHTML = origText; btn.disabled = false; if (result.success && result.rows.length > 0) { loadData(result.rows, result.usedPath); showBanner(`דוח עסקאות האשראי נשלף בהצלחה! נטענו <strong>${result.rows.length.toLocaleString('he-IL')}</strong> עסקאות (מוצגות מהחדש לישן).`, 'success'); } else { let errDesc = result.errorDetails || 'הקובץ לא נמצא או שהתקבלה תשובה ריקה.'; showBanner(`<strong>שגיאה בשליפת הדוח:</strong><br>${errDesc}`, 'danger'); } } async function callDirectApi(endpoint, path) { const params = new URLSearchParams(); params.append('token', appConfig.token); params.append('wath', path); params.append('convertType', 'json'); params.append('notLoadLang', '0'); const fullUrl = `${endpoint}?${params.toString()}`; try { const response = await fetch(fullUrl, { method: 'GET' }); if (!response.ok) { return { success: false, requestUrl: fullUrl, usedPath: path, errorDetails: `HTTP ${response.status} (${response.statusText})` }; } const text = await response.text(); let parsed = null; try { parsed = JSON.parse(text); if (parsed && (parsed.responseStatus === 'ERROR' || parsed.responseStatus === 'FORBIDDEN')) { const isNotFound = (parsed.message || '').toLowerCase().includes('not found') || parsed.messageCode === 2; return { success: false, requestUrl: fullUrl, usedPath: path, isFileNotFound: isNotFound, errorDetails: `שרת ימות המשיח החזיר: ${parsed.message || 'שגיאה'} (קוד שגיאה: ${parsed.messageCode || 'לא צוין'})` }; } } catch (e) {} let rows = []; if (parsed) { rows = extractRowsFromJson(parsed); } else { rows = extractRowsFromCsv(text); } return { success: rows.length > 0, rows: rows, requestUrl: fullUrl, usedPath: path }; } catch (err) { return { success: false, requestUrl: fullUrl, usedPath: path, errorDetails: `שגיאת תקשורת: ${err.message}` }; } } function extractRowsFromJson(json) { if (Array.isArray(json)) return json; if (json.data && Array.isArray(json.data)) return json.data; if (json.rows && Array.isArray(json.rows)) { const headers = json.headers || []; return json.rows.map(r => { let obj = {}; headers.forEach((h, idx) => { obj[h] = r[idx] !== undefined ? r[idx] : ''; }); return obj; }); } return [json]; } function extractRowsFromCsv(text) { const clean = text.trim(); if (!clean) return []; const rows = parseCSV(clean); if (rows.length < 2) return []; const headers = rows[0]; const data = []; for (let i = 1; i < rows.length; i++) { const r = rows[i]; if (r.length === 0 || (r.length === 1 && r[0] === '')) continue; let item = {}; headers.forEach((h, idx) => { item[h] = r[idx] !== undefined ? r[idx] : ''; }); data.push(item); } return data; } function parseCSV(text) { const firstLine = text.split(/\r\n|\n/)[0] || ''; const delimiter = firstLine.includes('\t') && !firstLine.includes(',') ? '\t' : ','; const rows = []; let currentRow = []; let currentVal = ''; let inQuotes = false; for (let i = 0; i < text.length; i++) { const c = text[i]; const next = text[i + 1]; if (c === '"') { if (inQuotes && next === '"') { currentVal += '"'; i++; } else { inQuotes = !inQuotes; } } else if (c === delimiter && !inQuotes) { currentRow.push(currentVal.trim()); currentVal = ''; } else if ((c === '\r' || c === '\n') && !inQuotes) { if (c === '\r' && next === '\n') i++; currentRow.push(currentVal.trim()); if (currentRow.some(col => col.length > 0)) rows.push(currentRow); currentRow = []; currentVal = ''; } else { currentVal += c; } } if (currentVal || currentRow.length > 0) { currentRow.push(currentVal.trim()); if (currentRow.some(col => col.length > 0)) rows.push(currentRow); } return rows; } async function handleLocalFile(e) { hideBanner(); const file = e.target.files[0]; if (!file) return; const content = await file.text(); let rows = []; try { if (file.name.endsWith('.json')) { rows = extractRowsFromJson(JSON.parse(content)); } else { rows = extractRowsFromCsv(content); } if (rows.length > 0) { loadData(rows, file.name); showBanner(`הקובץ "${file.name}" נטען בהצלחה! סה"כ ${rows.length.toLocaleString('he-IL')} עסקאות.`, 'success'); } else { showBanner('לא זוהו עסקאות בקובץ זה.', 'warning'); } } catch (err) { showBanner('שגיאה בקריאת הקובץ: ' + err.message, 'danger'); } } function resolveExtensionName(row, pathUsed) { let rawExt = ''; const extKeys = ['שלוחה', 'תיקיה', 'תיקייה', 'extension', 'Folder', 'folder', 'ApiExtension']; for (let k of extKeys) { if (row[k] !== undefined && String(row[k]).trim() !== '') { rawExt = String(row[k]).trim(); break; } } if (!rawExt && pathUsed) { const match = pathUsed.match(/ivr2:?\/?([^/]+)\/LogCreditCardOK\.ymgr/i); if (match && match[1]) { rawExt = match[1]; } else if (pathUsed.includes('LogCreditCardOK.ymgr')) { rawExt = 'ראשי'; } } if (!rawExt || rawExt === 'Log' || rawExt === '/') rawExt = 'ראשי'; if (appConfig.extensionAliases) { const pairs = appConfig.extensionAliases.split(/[,;\n]/); for (let p of pairs) { const [key, val] = p.split(':'); if (key && val && key.trim() === rawExt) { return val.trim(); } } } return rawExt === 'ראשי' ? 'שלוחה ראשית' : `שלוחה ${rawExt}`; } function applyExtensionNamesToData(pathUsed) { rawData.forEach(row => { row['תשלום ל...'] = resolveExtensionName(row, pathUsed); }); } function getRowDateTimeTimestamp(row) { let dVal = autoDateCol ? row[autoDateCol] : null; let tVal = autoTimeCol ? row[autoTimeCol] : null; if (!tVal) { const timeCandidates = ['שעה', 'time', 'Time']; for (let tc of timeCandidates) { if (row[tc]) { tVal = row[tc]; break; } } } if (dVal) { let combined = String(dVal).trim(); if (tVal) combined += ' ' + String(tVal).trim(); let parsed = parseDateValue(combined); if (parsed) return parsed.getTime(); } return null; } // טעינת הנתונים, סינון גורף של השדות המבוקשים והוספת "תשלום ל..." בראש function loadData(data, pathUsed) { rawData = data.map(origRow => { const cleanedRow = {}; cleanedRow['תשלום ל...'] = resolveExtensionName(origRow, pathUsed); // העתקה אך ורק של שדות שאינם ברשימת ההסרה Object.keys(origRow).forEach(key => { if (!isFieldExcluded(key) && key !== 'תשלום ל...') { cleanedRow[key] = origRow[key]; } }); return cleanedRow; }); if (rawData.length === 0) { allColumns = []; visibleColumns = []; applyFilters(); return; } // הפיכת סדר הרשומות כדי שהחדשות בתחתית הקובץ יוצגו ראשונות rawData.reverse(); // בניית רשימת העמודות הסופית (תשלום ל... תמיד ראשון) const colSet = new Set(['תשלום ל...']); rawData.forEach(item => { Object.keys(item).forEach(k => { if (!isFieldExcluded(k)) { colSet.add(k); } }); }); allColumns = Array.from(colSet); visibleColumns = [...allColumns]; detectSpecialColumns(); // ברירת מחדל: מהחדש לישן currentSort = { col: autoDateCol, asc: false }; populateColumnSelectors(); applyFilters(); } function detectSpecialColumns() { const dateCandidates = ['תאריך', 'date', 'Date', 'DateTime', 'התחלה', 'ApiTime']; autoDateCol = null; for (let c of dateCandidates) { const match = allColumns.find(col => col.toLowerCase().includes(c.toLowerCase())); if (match) { autoDateCol = match; break; } } const timeCandidates = ['שעה', 'time', 'Time']; autoTimeCol = null; for (let c of timeCandidates) { const match = allColumns.find(col => col.toLowerCase() === c.toLowerCase()); if (match) { autoTimeCol = match; break; } } const amountCandidates = ['סכום', 'amount', 'sum', 'billing_sum', 'סה"כ', 'מחיר']; amountCol = null; for (let c of amountCandidates) { const match = allColumns.find(col => col.toLowerCase().includes(c.toLowerCase())); if (match) { amountCol = match; break; } } } function populateColumnSelectors() { const dateSelect = document.getElementById('dateColumnSelect'); dateSelect.innerHTML = `<option value="">(אוטומטי: ${autoDateCol || 'לא זוהה'})</option>`; allColumns.forEach(c => { dateSelect.innerHTML += `<option value="${c}">${c}</option>`; }); const specificSelect = document.getElementById('specificColSelect'); specificSelect.innerHTML = `<option value="">כל העמודות</option>`; allColumns.forEach(c => { specificSelect.innerHTML += `<option value="${c}">${c}</option>`; }); } function parseDateValue(val) { if (!val) return null; val = String(val).trim(); const dmy = val.match(/^(\d{1,2})[\/\.-](\d{1,2})[\/\.-](\d{4})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/); if (dmy) { return new Date( parseInt(dmy[3], 10), parseInt(dmy[2], 10) - 1, parseInt(dmy[1], 10), parseInt(dmy[4] || 0, 10), parseInt(dmy[5] || 0, 10), parseInt(dmy[6] || 0, 10) ); } const ymd = val.match(/^(\d{4})[\/\.-](\d{1,2})[\/\.-](\d{1,2})(?:\s+(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/); if (ymd) { return new Date( parseInt(ymd[1], 10), parseInt(ymd[2], 10) - 1, parseInt(ymd[3], 10), parseInt(ymd[4] || 0, 10), parseInt(ymd[5] || 0, 10), parseInt(ymd[6] || 0, 10) ); } if (/^\d{10}$/.test(val)) return new Date(parseInt(val, 10) * 1000); if (/^\d{13}$/.test(val)) return new Date(parseInt(val, 10)); const standard = new Date(val); if (!isNaN(standard.getTime())) return standard; return null; } function applyFilters() { const globalTerm = document.getElementById('globalSearch').value.trim().toLowerCase(); const specCol = document.getElementById('specificColSelect').value; const specVal = document.getElementById('specificColValue').value.trim().toLowerCase(); const chosenDateCol = document.getElementById('dateColumnSelect').value || autoDateCol; const dateFromVal = document.getElementById('dateFrom').value; const dateToVal = document.getElementById('dateTo').value; const dateFrom = dateFromVal ? new Date(dateFromVal + 'T00:00:00') : null; const dateTo = dateToVal ? new Date(dateToVal + 'T23:59:59') : null; filteredData = rawData.filter(row => { if (chosenDateCol && (dateFrom || dateTo)) { const parsed = parseDateValue(row[chosenDateCol]); if (parsed) { if (dateFrom && parsed < dateFrom) return false; if (dateTo && parsed > dateTo) return false; } } if (globalTerm) { const matchGlobal = Object.values(row).some(v => String(v).toLowerCase().includes(globalTerm)); if (!matchGlobal) return false; } if (specCol && specVal) { const val = String(row[specCol] || '').toLowerCase(); if (!val.includes(specVal)) return false; } return true; }); // מיון: שילוב תאריך + שעה if (currentSort.col) { filteredData.sort((a, b) => { if (currentSort.col === autoDateCol) { const dtA = getRowDateTimeTimestamp(a); const dtB = getRowDateTimeTimestamp(b); if (dtA !== null && dtB !== null) { return currentSort.asc ? dtA - dtB : dtB - dtA; } } let va = a[currentSort.col] || ''; let vb = b[currentSort.col] || ''; const na = parseFloat(va); const nb = parseFloat(vb); if (!isNaN(na) && !isNaN(nb)) return currentSort.asc ? na - nb : nb - na; const da = parseDateValue(va); const db = parseDateValue(vb); if (da && db) return currentSort.asc ? da - db : db - da; return currentSort.asc ? String(va).localeCompare(String(vb), 'he') : String(vb).localeCompare(String(va), 'he'); }); } currentPage = 1; updateStats(); renderTable(); } function updateStats() { document.getElementById('statTotalRows').textContent = rawData.length.toLocaleString('he-IL'); document.getElementById('statFilteredRows').textContent = filteredData.length.toLocaleString('he-IL'); if (amountCol) { const totalSum = filteredData.reduce((acc, row) => { const raw = String(row[amountCol] || '').replace(/[^\d.-]/g, ''); const val = parseFloat(raw); return acc + (isNaN(val) ? 0 : val); }, 0); document.getElementById('statTotalSum').textContent = `₪${totalSum.toLocaleString('he-IL', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } else { document.getElementById('statTotalSum').textContent = '-'; } const phoneCandidates = ['טלפון', 'phone', 'Phone', 'callerId', 'callerIdNum', 'מספר מחייג']; let phoneCol = allColumns.find(c => phoneCandidates.some(pc => c.toLowerCase().includes(pc.toLowerCase()))); if (phoneCol) { const phones = new Set(filteredData.map(r => r[phoneCol]).filter(Boolean)); document.getElementById('statUniquePhones').textContent = phones.size.toLocaleString('he-IL'); } else { document.getElementById('statUniquePhones').textContent = '-'; } } function setQuickDate(type) { document.querySelectorAll('.quick-dates .chip-btn').forEach(b => b.classList.remove('active')); event.target.classList.add('active'); const now = new Date(); const dateFrom = document.getElementById('dateFrom'); const dateTo = document.getElementById('dateTo'); function formatYMD(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } if (type === 'today') { dateFrom.value = formatYMD(now); dateTo.value = formatYMD(now); } else if (type === 'yesterday') { const y = new Date(); y.setDate(now.getDate() - 1); dateFrom.value = formatYMD(y); dateTo.value = formatYMD(y); } else if (type === '7days') { const prev = new Date(); prev.setDate(now.getDate() - 6); dateFrom.value = formatYMD(prev); dateTo.value = formatYMD(now); } else if (type === '30days') { const prev = new Date(); prev.setDate(now.getDate() - 29); dateFrom.value = formatYMD(prev); dateTo.value = formatYMD(now); } else if (type === 'month') { const first = new Date(now.getFullYear(), now.getMonth(), 1); dateFrom.value = formatYMD(first); dateTo.value = formatYMD(now); } else if (type === 'all') { dateFrom.value = ''; dateTo.value = ''; } applyFilters(); } function resetFilters() { document.getElementById('globalSearch').value = ''; document.getElementById('specificColSelect').value = ''; document.getElementById('specificColValue').value = ''; document.getElementById('dateFrom').value = ''; document.getElementById('dateTo').value = ''; document.querySelectorAll('.quick-dates .chip-btn').forEach(b => b.classList.remove('active')); currentSort = { col: autoDateCol, asc: false }; applyFilters(); } function renderTable() { const thead = document.getElementById('tableHead'); const tbody = document.getElementById('tableBody'); if (filteredData.length === 0) { thead.innerHTML = `<tr><th>#</th><th>תוצאה</th></tr>`; tbody.innerHTML = `<tr><td colspan="2" style="text-align: center; padding: 2.5rem; color: var(--text-muted);">לא נמצאו עסקאות התואמות את הסינון</td></tr>`; renderPagination(); return; } let headHtml = `<tr><th style="width: 50px; text-align: center;">#</th>`; visibleColumns.forEach(c => { let isSorted = currentSort.col === c; let sortIcon = '↕'; if (isSorted) { sortIcon = currentSort.asc ? '▲ (מהישן לחדש)' : '▼ (מהחדש לישן)'; } headHtml += `<th class="${isSorted ? 'sorted-col' : ''}" onclick="sortTable('${c}')" title="לחץ לשינוי סדר המיון"> <span class="sort-icon">${sortIcon}</span> ${c} </th>`; }); headHtml += `</tr>`; thead.innerHTML = headHtml; let start = (currentPage - 1) * pageSize; let end = pageSize === 'all' ? filteredData.length : start + pageSize; if (pageSize === 'all') start = 0; const pageRows = filteredData.slice(start, end); const decodeYemot = document.getElementById('decodeYemotCharsCheck').checked; let bodyHtml = ''; pageRows.forEach((row, idx) => { const rowNum = start + idx + 1; bodyHtml += `<tr onclick="openModal(${rowNum - 1})"><td style="text-align: center; font-weight: 600; color: var(--text-muted);">${rowNum}</td>`; visibleColumns.forEach(c => { let val = row[c] !== undefined ? String(row[c]) : ''; if (decodeYemot && (val.includes('^') || val.includes('*'))) { val = val.replace(/\^/g, '=').replace(/\*/g, '&'); } if (c === 'תשלום ל...') { bodyHtml += `<td><span class="badge-payment-for">${escapeHtml(val)}</span></td>`; return; } if (c === amountCol && !isNaN(parseFloat(val))) { val = `₪${parseFloat(val).toLocaleString('he-IL', { minimumFractionDigits: 2 })}`; bodyHtml += `<td style="font-weight: 700; color: #059669;">${val}</td>`; return; } bodyHtml += `<td title="${escapeHtml(val)}">${escapeHtml(val)}</td>`; }); bodyHtml += `</tr>`; }); tbody.innerHTML = bodyHtml; renderPagination(); } function escapeHtml(str) { return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); } function sortTable(col) { if (currentSort.col === col) { currentSort.asc = !currentSort.asc; } else { currentSort.col = col; currentSort.asc = (col === autoDateCol) ? false : true; } applyFilters(); } function changePageSize() { const val = document.getElementById('pageSizeSelect').value; pageSize = val === 'all' ? 'all' : parseInt(val, 10); currentPage = 1; renderTable(); } function renderPagination() { const total = filteredData.length; const nav = document.getElementById('paginationNav'); const info = document.getElementById('paginationInfo'); if (total === 0 || pageSize === 'all') { info.textContent = `מציג ${total.toLocaleString('he-IL')} מתוך ${total.toLocaleString('he-IL')} עסקאות`; nav.innerHTML = ''; return; } const totalPages = Math.ceil(total / pageSize); const start = (currentPage - 1) * pageSize + 1; const end = Math.min(currentPage * pageSize, total); info.textContent = `מציג ${start.toLocaleString('he-IL')}-${end.toLocaleString('he-IL')} מתוך ${total.toLocaleString('he-IL')} עסקאות`; let navHtml = ''; navHtml += `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="goToPage(1)">«</button>`; navHtml += `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="goToPage(${currentPage - 1})">‹</button>`; let pStart = Math.max(1, currentPage - 2); let pEnd = Math.min(totalPages, currentPage + 2); for (let p = pStart; p <= pEnd; p++) { navHtml += `<button class="page-btn ${p === currentPage ? 'active' : ''}" onclick="goToPage(${p})">${p}</button>`; } navHtml += `<button class="page-btn" ${currentPage === totalPages ? 'disabled' : ''} onclick="goToPage(${currentPage + 1})">›</button>`; navHtml += `<button class="page-btn" ${currentPage === totalPages ? 'disabled' : ''} onclick="goToPage(${totalPages})">»</button>`; nav.innerHTML = navHtml; } function goToPage(p) { currentPage = p; renderTable(); } function openModal(indexInFiltered) { const row = filteredData[indexInFiltered]; if (!row) return; document.getElementById('modalTitle').textContent = `פירוט עסקה #${indexInFiltered + 1}`; const content = document.getElementById('modalContent'); content.innerHTML = ''; const decodeYemot = document.getElementById('decodeYemotCharsCheck').checked; visibleColumns.forEach(c => { let val = row[c] !== undefined ? String(row[c]) : ''; let decodedVal = ''; if (decodeYemot && (val.includes('^') || val.includes('*'))) { decodedVal = val.replace(/\^/g, '=').replace(/\*/g, '&'); } const div = document.createElement('div'); div.style.cssText = 'display: grid; grid-template-columns: 140px 1fr; padding: 0.55rem 0; border-bottom: 1px solid #f1f5f9; gap: 0.5rem; font-size: 0.9rem;'; div.innerHTML = ` <div style="font-weight: 600; color: var(--text-muted);">${escapeHtml(c)}</div> <div style="word-break: break-all;"> <span style="${c === amountCol ? 'font-weight: 700; color: #059669;' : ''}">${escapeHtml(val || '-')}</span> ${decodedVal ? `<div style="margin-top: 4px; color: var(--primary); font-size: 0.82rem;"><strong>מפוענח:</strong> ${escapeHtml(decodedVal)}</div>` : ''} </div> `; content.appendChild(div); }); document.getElementById('detailModal').classList.add('active'); } function closeModal(e) { if (!e || e.target.classList.contains('modal-overlay') || e.target.classList.contains('close-btn')) { document.getElementById('detailModal').classList.remove('active'); } } function exportData(type) { if (filteredData.length === 0) { alert('אין נתונים מסוננים לייצוא.'); return; } if (type === 'json') { const jsonStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(filteredData, null, 2)); downloadFile(jsonStr, "LogCreditCardOK_Clean.json"); } else if (type === 'csv') { let csvContent = "\uFEFF"; csvContent += visibleColumns.map(c => `"${c.replace(/"/g, '""')}"`).join(",") + "\r\n"; filteredData.forEach(row => { let line = visibleColumns.map(c => { let v = row[c] !== undefined ? String(row[c]) : ""; return `"${v.replace(/"/g, '""')}"`; }).join(","); csvContent += line + "\r\n"; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); downloadFile(url, "LogCreditCardOK_Clean.csv"); } } function downloadFile(url, filename) { const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); } </script> </body> </html> -
@אA לאיפה זה?
ממש תודה -
@אA וואו יפה מאוד!
תודה רבה. -
@מוטי-מוטי-מוטי איפה פתחת את זה?
-
@החברמן
זה קובץ html
תפתח קובץ פנקס רשימות, בתוכו תכניס את הקוד >קובץ>שמור בשם>תוכנה לדוחות סליקה.html>שמור, והתוכנה תישמר. -
ה החברמן סימן נושא זה כשאלה
-
ה החברמן סימן נושא זה כנפתר
-
@אA ואאוווווו תודה רבה!!!!
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות