• דף הבית
    • אינדקס קישורים
    • פוסטים אחרונים
    • משתמשים
    • חיפוש בהגדרות המתקדמות
    • חיפוש גוגל בפורום
    • ניהול המערכת
    • ניהול המערכת - שרת private
    • הרשמה
    • התחברות
    1. דף הבית
    2. עובד את השם
    3. פוסטים
    ע
    מנותק
    • פרופיל
    • עוקב אחרי 0
    • עוקבים 0
    • נושאים 2
    • פוסטים 47
    • קבוצות 0

    פוסטים

    פוסטים אחרונים הגבוה ביותר שנוי במחלוקת
    • RE: שיחה חוזרת ממספר אחר

      @זעליג לא, מספר אחר סתם.
      (אם הבנתי למה התכוונת, אבל תסביר ליתר ביטחון)

      פורסם בבקשות לפיתוח
      ע
      עובד את השם
    • שיחה חוזרת ממספר אחר

      אשמח אם מישהו יפתח (או שיש כבר), הגדרה בקו שאפשר לקבל שיחה ממספר אחר של ימות המשיח, דוגמא: אני מתקשר ל-077-1234567 ומקבל שיחה חוזרת ממספר - 0772222770, בדומה למודול שיחה חוזרת.
      תודה רבה.

      פורסם בבקשות לפיתוח
      ע
      עובד את השם
    • RE: מערכת קבוצה חברתית

      @hgbeh אם הבנתי מה אתה מתכוון, הוא שאל אותך אם להוריד פרסומות , צריך להקיש 0, זה עוקף את השאלה.

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: אני צריך דוח על שלוחה מסוימת איך אני מוציא את זה

      @הפצת-התורה תפתח ת'דף

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: אני צריך דוח על שלוחה מסוימת איך אני מוציא את זה

      @זרח הוא רצה דוחות על שלוחות מסויימות ויש שם.

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: שידור חי אינטרנטי תקלה

      מצטער שאני לא יכול להצביע לכם, פשוט אני משתמש די חדש.

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: אני צריך דוח על שלוחה מסוימת איך אני מוציא את זה

      @הפצת-התורה יש לך את זה וגם את זה:
      (תפתח בפנקס רשימות ותשמור בשם עם סיומת HTML)

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
          <meta charset="UTF-8">
          <title>ניתוח נתוני מערכות | גרסה יציבה ומדויקת</title>
          <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
          <style>
              :root { --bg: #0f172a; --card: #1e293b; --primary: #38bdf8; --accent: #22c55e; --text: #f1f5f9; --border: #334155; --danger: #ef4444; --warning: #f59e0b; }
              body { font-family: 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); margin: 0; display: flex; flex-direction: column; height: 100vh; }
              header { background: #1e293b; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); }
              .logo-box { display: flex; align-items: center; gap: 12px; }
              .logo-icon { background: linear-gradient(135deg, var(--primary), #0ea5e9); color: #0f172a; width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; }
              .logo-text { font-size: 1.4rem; font-weight: 800; color: var(--text); }
              .config-box { display: flex; gap: 12px; align-items: center; }
              input, select { background: #0f172a; border: 1px solid var(--border); color: white; padding: 10px; border-radius: 8px; outline: none; }
              input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); cursor: pointer; }
              .btn-run { background: var(--primary); color: #0f172a; border: none; padding: 10px 25px; border-radius: 8px; font-weight: bold; cursor: pointer; transition: 0.3s; }
              .btn-run:hover { opacity: 0.8; }
              .nav-tabs { background: #1e293b; display: flex; padding: 0 20px; border-bottom: 1px solid var(--border); }
              .tab { padding: 15px 25px; cursor: pointer; color: #94a3b8; border-bottom: 3px solid transparent; transition: 0.3s; }
              .tab.active { color: var(--primary); border-bottom-color: var(--primary); background: rgba(56, 189, 248, 0.07); }
              .main-content { flex: 1; padding: 25px; overflow-y: auto; background: #0f172a; }
              .card { background: var(--card); padding: 25px; border-radius: 16px; border: 1px solid var(--border); margin-bottom: 25px; }
              .stat-num { font-size: 2.5rem; font-weight: 800; color: var(--primary); }
              .control-bar { display: flex; gap: 20px; align-items: center; background: #334155; padding: 15px 25px; border-radius: 12px 12px 0 0; }
              .search-input { background: #0f172a; border: 1px solid var(--border); color: white; padding: 8px 15px; border-radius: 8px; width: 250px; }
              table { width: 100%; border-collapse: collapse; background: var(--card); border-radius: 0 0 12px 12px; overflow: hidden; }
              th { text-align: right; padding: 15px; background: #475569; color: white; font-size: 0.85rem; }
              td { padding: 15px; border-bottom: 1px solid var(--border); font-size: 0.95rem; }
              tr.clickable { cursor: pointer; }
              tr.clickable:hover { background: rgba(56, 189, 248, 0.1); }
              .bar-container { width: 100px; background: #0f172a; height: 10px; border-radius: 10px; display: inline-block; overflow: hidden; vertical-align: middle; margin-left: 8px; }
              .bar-fill { height: 100%; background: var(--accent); }
              .modal-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.85); display: none; justify-content: center; align-items: center; z-index: 1000; }
              .modal-content { background: var(--card); width: 85%; max-height: 85%; border-radius: 20px; border: 1px solid var(--primary); overflow-y: auto; padding: 30px; position: relative; }
              .close-modal { position: absolute; top: 20px; left: 20px; color: white; font-size: 2rem; cursor: pointer; }
              .progress-container { width: 100%; background: #1e293b; height: 20px; display: none; position: relative; text-align: center; }
              #progress-fill { height: 100%; background: var(--primary); width: 0%; transition: 0.4s; position: absolute; top:0; right:0; }
              #progress-text { position: relative; z-index: 2; font-size: 0.8rem; font-weight: bold; color: white; line-height: 20px; }
              .hidden { display: none; }
              .live-status { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; padding: 4px 10px; border-radius: 20px; background: #334155; }
              .dot { width: 8px; height: 8px; border-radius: 50%; background: #666; }
              .dot.active { background: var(--accent); animation: pulse 2s infinite; }
              @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }
          </style>
      </head>
      <body>
       
      <header>
          <div class="logo-box">
              <div class="logo-icon"><i class="fas fa-chart-pie"></i></div>
              <div class="logo-text">ניתוח נתוני מערכות</div>
              <div class="live-status"><div id="live-dot" class="dot"></div> <span id="live-text">OFFLINE</span></div>
          </div>
          <div class="config-box">
              <input type="password" id="apiToken" placeholder="טוקן API">
              <input type="date" id="startDate">
              <input type="date" id="endDate">
              <button class="btn-run" onclick="manualStart()">הפעל ניתוח</button>
          </div>
      </header>
       
      <div class="progress-container" id="progBar">
          <div id="progress-fill"></div>
          <span id="progress-text">0%</span>
      </div>
       
      <div class="nav-tabs">
          <div class="tab active" onclick="switchTab(event, 'dash-tab')">דאשבורד</div>
          <div class="tab" onclick="switchTab(event, 'ext-tab')">שלוחות</div>
          <div class="tab" onclick="switchTab(event, 'play-tab')">השמעות</div>
          <div class="tab" onclick="switchTab(event, 'users-tab')">מאזינים</div>
          <div class="tab" style="color: var(--warning)" onclick="switchTab(event, 'debug-tab')">לוג המערכת</div>
      </div>
       
      <div class="main-content">
          <div id="dash-tab" class="tab-content">
              <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 25px; margin-bottom: 25px;">
                  <div class="card"><span style="color: #94a3b8">מאזינים ייחודיים בטבלה</span><br><span id="stat-users" class="stat-num">0</span></div>
                  <div class="card"><span style="color: #94a3b8">סה"כ דקות שיחה (מערכת)</span><br><span id="stat-min" class="stat-num">0</span></div>
                  <div class="card"><span style="color: #94a3b8">סה"כ השמעות שהושלמו עד הסוף</span><br><span id="stat-comp" class="stat-num">0</span></div>
              </div>
              <div class="card"><h3>גרף פעילות יומי</h3><canvas id="dailyChart" height="100"></canvas></div>
          </div>
       
          <div id="ext-tab" class="tab-content hidden">
              <div class="control-bar">
                  <select id="extSort" onchange="renderExts()"><option value="count">כמות כניסות</option><option value="sec">זמן שהייה</option></select>
                  <input type="text" id="extSearch" class="search-input" placeholder="חפש שלוחה..." onkeyup="renderExts()">
              </div>
              <table id="extTable"><thead><tr><th>שם השלוחה</th><th>תיאור שלוחה</th><th>כניסות</th><th>דקות שהייה</th><th>ממוצע</th></tr></thead><tbody></tbody></table>
          </div>
       
          <div id="play-tab" class="tab-content hidden">
              <div class="control-bar">
                  <select id="playSort" onchange="renderPlays()"><option value="count">פופולריות</option><option value="avg">אחוז השלמה</option></select>
                  <input type="text" id="playSearch" class="search-input" placeholder="חפש קובץ..." onkeyup="renderPlays()">
              </div>
              <table id="playTable"><thead><tr><th>שם קובץ</th><th>שלוחה</th><th>מספר השמעות</th><th>סה"כ דקות</th><th>אחוז השלמה</th><th>נטישה</th></tr></thead><tbody></tbody></table>
          </div>
       
          <div id="users-tab" class="tab-content hidden">
              <div class="control-bar">
                  <select id="userSort" onchange="renderUsers()"><option value="sec">זמן האזנה כולל</option><option value="calls">מספר שיחות</option></select>
                  <input type="text" id="userSearch" class="search-input" placeholder="חפש טלפון או שם..." onkeyup="renderUsers()">
              </div>
              <table id="usersTable"><thead><tr><th>מספר טלפון</th><th>שם מאזין</th><th>דקות האזנה</th><th>מספר שיחות</th><th>סטטוס</th></tr></thead><tbody></tbody></table>
          </div>
       
          <div id="debug-tab" class="tab-content hidden">
              <div class="card"><div id="debug-console" style="background:#000; color:#22c55e; padding:20px; height:400px; overflow-y:auto; font-family:monospace; border-radius: 8px;"></div></div>
          </div>
      </div>
       
      <!-- מודאל פירוט מאזין -->
      <div id="userModal" class="modal-overlay" onclick="closeModal()">
          <div class="modal-content" onclick="event.stopPropagation()">
              <span class="close-modal" onclick="closeModal()">&times;</span>
              <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
                  <h2 id="modalTitle" style="color: var(--primary); margin:0;"></h2>
                  <select id="modalFilter" onchange="filterModalContent()" style="background: var(--bg); color:white; border: 1px solid var(--primary); padding: 5px 15px; border-radius: 5px;">
                      <option value="all">הכל</option>
                      <option value="שלוחה">שלוחות בלבד</option>
                      <option value="השמעה">השמעות בלבד</option>
                  </select>
              </div>
              <table id="modalTable">
                  <thead>
                      <tr><th>סוג פעילות</th><th>שלוחה/קובץ</th><th>זמן שהייה</th><th>תאריך (עברי ולועזי)</th></tr>
                  </thead>
                  <tbody></tbody>
              </table>
          </div>
      </div>
       
      <!-- מודאל פירוט שלוחה -->
      <div id="extModal" class="modal-overlay" onclick="closeExtModal()">
          <div class="modal-content" onclick="event.stopPropagation()">
              <span class="close-modal" onclick="closeExtModal()">&times;</span>
              <h2 id="extModalTitle" style="color: var(--primary); margin-bottom: 20px;"></h2>
              <table id="extModalTable">
                  <thead>
                      <tr>
                          <th>שם מאזין</th>
                          <th>מספר טלפון</th>
                          <th>כמות כניסות (לשלוחה זו)</th>
                          <th>זמן שהייה (בשלוחה זו)</th>
                      </tr>
                  </thead>
                  <tbody></tbody>
              </table>
          </div>
      </div>
       
      <script>
          const API = "https://www.call2all.co.il/ym/api/";
          let dataStore = { exts: {}, plays: {}, users: {}, daily: {}, rawActivity: [], totalSec: 0, completed: 0, names: {} };
          let currentModalPhone = "";
          let dailyChart = null;
          let autoRefreshTimer = null;
       
          function addLog(msg, color = '#22c55e') {
              const console = document.getElementById('debug-console');
              if(!console) return;
              const div = document.createElement('div');
              div.style.color = color;
              div.innerText = `[${new Date().toLocaleTimeString()}] ${msg}`;
              console.appendChild(div);
              console.scrollTop = console.scrollHeight; 
          }
       
          function normalizePhone(p) {
              if (!p) return "חסוי";
              let s = p.toString().replace(/\D/g, '');
              if (s.length > 0 && !s.startsWith('0') && s.length >= 8) s = '0' + s;
              return s;
          }
       
          function formatExtName(ext) {
              if (!ext) return "ראשית";
              let str = ext.toString().trim();
              if (str === "" || str === "/" || str.toLowerCase() === "main" || str.toLowerCase() === "root") {
                  return "ראשית";
              }
              return str;
          }
       
          function parseYemotDateToIso(dStr) {
              if (!dStr || !dStr.includes('/')) return null;
              const parts = dStr.split('/');
              if (parts.length === 3) {
                  const d = parts[0].padStart(2, '0');
                  const m = parts[1].padStart(2, '0');
                  const y = parts[2];
                  return `${y}-${m}-${d}`;
              }
              return null;
          }
       
          function timeStrToSeconds(tStr) {
              if (!tStr) return 0;
              if (!tStr.includes(':')) return parseInt(tStr) || 0;
              const p = tStr.split(':').map(Number);
              if (p.length === 3) return (p[0] * 3600) + (p[1] * 60) + p[2];
              if (p.length === 2) return (p[0] * 60) + p[1];
              return parseInt(tStr) || 0;
          }
       
          function ensureUser(phone) {
              if (!dataStore.users[phone]) {
                  dataStore.users[phone] = { sec: 0, calls: 0 };
              }
          }
       
          async function fetchNames(token) {
              try {
                  const res = await fetch(`${API}DownloadFile?token=${token}&path=ivr2:/EnterIDValName.ini`);
                  const text = await res.text();
                  if(text && !text.includes("NOT_FOUND")) {
                      text.split(/\r?\n/).forEach(line => {
                          if (line.includes('=')) {
                              const [phone, name] = line.split('=');
                              const cleanPhone = normalizePhone(phone);
                              if (cleanPhone && name) dataStore.names[cleanPhone] = name.trim();
                          }
                      });
                      addLog(`נטענו שמות מקובץ ההגדרות הראשי`);
                  }
              } catch(e) { }
          }
       
          async function manualStart() {
              if(autoRefreshTimer) clearInterval(autoRefreshTimer);
              await startFullAnalysis();
              document.getElementById('live-dot').classList.add('active');
              document.getElementById('live-text').innerText = "LIVE (פעיל)";
          }
       
          async function startFullAnalysis() {
              const token = document.getElementById('apiToken').value;
              const startStr = document.getElementById('startDate').value; 
              const endStr = document.getElementById('endDate').value;     
              
              if (!token) return alert("נא להזין טוקן API");
       
              addLog(`מתחיל עיבוד נתונים מתאריך ${startStr} עד ${endStr}...`);
              document.getElementById('progBar').style.display = 'block';
              updateProgress(5, 100);
              
              dataStore = { exts: {}, plays: {}, users: {}, daily: {}, rawActivity: [], totalSec: 0, completed: 0, names: {} };
              
              await fetchNames(token);
              updateProgress(10, 100);
       
              // חישוב חודשים בטוח (מניעת תקלות של אזור זמן)
              let currM = new Date(startStr);
              currM.setDate(1); // עקיפת בעיית 31 לחודש
              let endM = new Date(endStr);
              let monthsToFetch = [];
              while (currM <= endM || (currM.getFullYear() === endM.getFullYear() && currM.getMonth() === endM.getMonth())) {
                  let y = currM.getFullYear();
                  let m = String(currM.getMonth() + 1).padStart(2, '0');
                  monthsToFetch.push(`${y}-${m}`);
                  currM.setMonth(currM.getMonth() + 1);
              }
       
              // אובייקט לאיתור שיחות כפולות (למניעת קפיצת דקות)
              let callMap = {};
       
              for (let month of monthsToFetch) {
                  try {
                      addLog(`מייבא לוג כניסות עבור חודש ${month}...`);
                      const res = await fetch(`${API}RenderYMGRFile?token=${token}&wath=ivr2:/Log/LogFolderEnterExit-${month}.ymgr&convertType=json`);
                      const json = await res.json();
                      
                      if (json.data && Array.isArray(json.data)) {
                          json.data.forEach(row => {
                              const gregDate = row["תאריך"]; 
                              const hebDate = row["תאריך עברי"] || row["עברי"] || ""; 
                              const isoDate = parseYemotDateToIso(gregDate);
                              
                              if (!isoDate) return;
                              
                              // סינון מוחלט של ימים לפי טקסט (למשל "2026-03-31") - מונע כפילויות!
                              if (isoDate >= startStr && isoDate <= endStr) {
                                  
                                  const extName = formatExtName(row["שלוחה"]);
                                  const phone = normalizePhone(row["טלפון"]);
                                  const sec = parseInt(row["סה\"כ שניות"]) || 0;
                                  const callId = row["מזהה שיחה"];
                                  const nameFromLog = row["שם מזהה"];
                                  
                                  if (nameFromLog && !dataStore.names[phone]) dataStore.names[phone] = nameFromLog;
       
                                  // -- חישוב נתונים לשלוחות --
                                  if (!dataStore.exts[extName]) {
                                      dataStore.exts[extName] = { count: 0, sec: 0, title: row["כותרת שלוחה"] || "", extUsers: {} };
                                  }
                                  dataStore.exts[extName].count++;
                                  dataStore.exts[extName].sec += sec;
       
                                  if (!dataStore.exts[extName].extUsers[phone]) {
                                      dataStore.exts[extName].extUsers[phone] = { count: 0, sec: 0 };
                                  }
                                  dataStore.exts[extName].extUsers[phone].count++;
                                  dataStore.exts[extName].extUsers[phone].sec += sec;
       
                                  // -- איסוף זמן למאזין וכללי במערכת (עם מניעת כפילויות של מזהה שיחה) --
                                  if (callId) {
                                      if (!callMap[callId]) callMap[callId] = { phone: phone, maxSec: 0 };
                                      if (sec > callMap[callId].maxSec) callMap[callId].maxSec = sec;
                                  } else {
                                      // במקרה נדיר שאין מזהה שיחה
                                      ensureUser(phone);
                                      dataStore.users[phone].sec += sec;
                                      dataStore.users[phone].calls++;
                                      dataStore.totalSec += sec;
                                  }
       
                                  if (!dataStore.daily[isoDate]) dataStore.daily[isoDate] = 0;
                                  dataStore.daily[isoDate]++;
       
                                  const displayDate = hebDate ? `${hebDate} (${gregDate})` : gregDate;
                                  dataStore.rawActivity.push({ phone, type: 'שלוחה', name: extName, sec: sec + " ש'", date: `${displayDate} ${row["התחלה שעה"]||''}` });
                              }
                          });
                      }
                  } catch(e) { addLog(`לא נמצא לוג כניסות בחודש ${month}`, "orange"); }
              }
       
              // עדכון סך הדקות והשיחות במערכת (מסונן ללא כפילויות!)
              Object.values(callMap).forEach(call => {
                  dataStore.totalSec += call.maxSec;
                  ensureUser(call.phone);
                  dataStore.users[call.phone].sec += call.maxSec;
                  dataStore.users[call.phone].calls += 1;
              });
       
              updateProgress(50, 100);
       
              // חישוב ימים להשמעות בטוח
              let daysToFetch = [];
              let currDay = new Date(startStr);
              let endDay = new Date(endStr);
              while(currDay <= endDay) {
                  let y = currDay.getFullYear();
                  let m = String(currDay.getMonth() + 1).padStart(2, '0');
                  let d = String(currDay.getDate()).padStart(2, '0');
                  daysToFetch.push(`${y}-${m}-${d}`);
                  currDay.setDate(currDay.getDate() + 1);
              }
       
              for (let i=0; i<daysToFetch.length; i++) {
                  const day = daysToFetch[i];
                  try {
                      const res = await fetch(`${API}RenderYMGRFile?token=${token}&wath=ivr2:/Log/LogPlaybackPlayStop/LogPlaybackPlayStop.${day}.ymgr&convertType=json`);
                      const json = await res.json();
                      
                      if (json.data && Array.isArray(json.data)) {
                          json.data.forEach(row => {
                              const gregDate = row["תאריך"] || day;
                              const isoDate = parseYemotDateToIso(gregDate);
                              
                              // וידוא שההשמעה בטווח התאריכים המדויק
                              if (isoDate && (isoDate < startStr || isoDate > endStr)) return;
       
                              const file = row["השמעה"];
                              if (!file) return;
       
                              const folder = formatExtName(row["שלוחה"]);
                              const phone = normalizePhone(row["טלפון"]);
                              const sec = parseInt(row["סה\"כ שניות"]) || 0;
                              const fileLenSec = timeStrToSeconds(row["אורך הקובץ"]);
                              const exitPoint = row["נקודת יציאה"];
                              const nameFromLog = row["שם"];
                              
                              const hebDate = row["עברי"] || row["תאריך עברי"] || "";
                              const displayDate = hebDate ? `${hebDate} (${gregDate})` : gregDate;
                              
                              if (nameFromLog && !dataStore.names[phone]) dataStore.names[phone] = nameFromLog;
                              ensureUser(phone); // מוודא שמי שהאזין לקובץ יופיע בטבלת מאזינים גם אם לא נכנס לשלוחה רשמית
       
                              const isEnd = (exitPoint === "סוף") || (fileLenSec > 0 && sec >= fileLenSec - 2);
                              if (isEnd) dataStore.completed++;
       
                              if (!dataStore.plays[file]) dataStore.plays[file] = { count: 0, sec: 0, drops: 0, pcts: [], folder: folder };
                              dataStore.plays[file].count++;
                              dataStore.plays[file].sec += sec;
                              
                              const pct = fileLenSec > 0 ? Math.min(100, Math.round((sec / fileLenSec) * 100)) : (isEnd ? 100 : 0);
                              dataStore.plays[file].pcts.push(pct);
                              
                              if (!isEnd) dataStore.plays[file].drops++;
       
                              dataStore.rawActivity.push({ phone, type: 'השמעה', name: file, sec: sec + " ש'", date: `${displayDate} ${row["התחלה שעה"]||''}` });
                          });
                      }
                  } catch(e) {}
                  
                  updateProgress(50 + Math.round(((i + 1) / daysToFetch.length) * 50), 100);
              }
       
              addLog("עיבוד הנתונים הסתיים בהצלחה.");
              renderUI();
          }
       
          function updateProgress(c, t) { 
              const pct = Math.round((c/t)*100);
              document.getElementById('progress-fill').style.width = pct + '%'; 
              document.getElementById('progress-text').innerText = pct + '%';
          }
       
          function renderUI() {
              document.getElementById('progBar').style.display = 'none';
              
              // כאן הסנכרון המושלם - הדאשבורד מציג בדיוק את מספר המאזינים שיש בטבלה
              document.getElementById('stat-users').innerText = Object.keys(dataStore.users).length.toLocaleString();
              
              document.getElementById('stat-min').innerText = Math.floor(dataStore.totalSec / 60).toLocaleString();
              document.getElementById('stat-comp').innerText = dataStore.completed.toLocaleString();
              
              renderExts(); 
              renderPlays(); 
              renderUsers(); 
              updateChart();
          }
       
          function renderExts() {
              const sort = document.getElementById('extSort').value;
              const q = document.getElementById('extSearch').value.toLowerCase();
              let items = Object.entries(dataStore.exts).map(([name, d]) => ({name, ...d})).filter(i => i.name.toLowerCase().includes(q));
              items.sort((a,b) => b[sort] - a[sort]);
              document.querySelector('#extTable tbody').innerHTML = items.map(i => `
                  <tr class="clickable" onclick="openExtDetail('${i.name}')">
                      <td style="color:var(--primary); font-weight:600">${i.name}</td>
                      <td>${i.title || '-'}</td>
                      <td>${i.count}</td>
                      <td>${(i.sec/60).toFixed(1)}</td>
                      <td>${i.count ? Math.round(i.sec/i.count) : 0} ש'</td>
                  </tr>
              `).join('');
          }
       
          function renderPlays() {
              const sort = document.getElementById('playSort').value;
              const q = document.getElementById('playSearch').value.toLowerCase();
              let items = Object.entries(dataStore.plays).map(([name, d]) => {
                  const avg = d.pcts && d.pcts.length ? Math.round(d.pcts.reduce((a,b)=>a+b,0)/d.pcts.length) : 0;
                  const drop = d.count ? Math.round((d.drops/d.count)*100) : 0;
                  return {name, avg, drop, ...d};
              }).filter(i => i.name.toLowerCase().includes(q));
              items.sort((a,b) => sort === 'count' ? b.count - a.count : b.avg - a.avg);
              document.querySelector('#playTable tbody').innerHTML = items.map(i => `
                  <tr><td>${i.name}</td><td>${i.folder || '-'}</td><td>${i.count}</td><td>${(i.sec/60).toFixed(1)}</td>
                  <td><div class="bar-container"><div class="bar-fill" style="width:${i.avg}%"></div></div> ${i.avg}%</td>
                  <td style="color:${i.drop > 50 ? 'var(--danger)' : 'var(--accent)'}">${i.drop}%</td></tr>
              `).join('');
          }
       
          function renderUsers() {
              const sortKey = document.getElementById('userSort').value;
              const q = document.getElementById('userSearch').value;
              let items = Object.entries(dataStore.users).map(([phone, d]) => ({
                  phone, name: dataStore.names[phone] || "לא ידוע", sec: d.sec || 0, calls: d.calls || 0
              })).filter(i => i.phone.includes(q) || i.name.includes(q));
              items.sort((a,b) => b[sortKey] - a[sortKey]);
              document.querySelector('#usersTable tbody').innerHTML = items.map((i, idx) => `
                  <tr class="clickable" onclick="openUserDetail('${i.phone}')">
                      <td style="font-weight:bold">${i.phone}</td><td style="color:var(--warning)">${i.name}</td>
                      <td style="color:var(--primary); font-weight:bold">${(i.sec/60).toFixed(1)}</td><td>${i.calls}</td>
                      <td>${idx < 3 ? '🏆 מוביל' : 'מאזין'}</td>
                  </tr>
              `).join('');
          }
       
          function openUserDetail(phone) {
              currentModalPhone = phone;
              document.getElementById('modalTitle').innerText = `פירוט מאזין: ${phone} (${dataStore.names[phone] || "לא ידוע"})`;
              filterModalContent();
              document.getElementById('userModal').style.display = 'flex';
          }
       
          function filterModalContent() {
              const filter = document.getElementById('modalFilter').value;
              const activity = dataStore.rawActivity.filter(a => a.phone === currentModalPhone);
              const filtered = filter === 'all' ? activity : activity.filter(a => a.type === filter);
              document.querySelector('#modalTable tbody').innerHTML = filtered.map(a => `
                  <tr><td style="color:${a.type==='השמעה'?'var(--primary)':'var(--accent)'}; font-weight:bold">${a.type}</td>
                  <td>${a.name}</td><td>${a.sec}</td><td>${a.date}</td></tr>
              `).join('');
          }
       
          function closeModal() { document.getElementById('userModal').style.display = 'none'; }
          
          function openExtDetail(extName) {
              document.getElementById('extModalTitle').innerText = `פירוט מאזינים בשלוחה: ${extName}`;
              const extData = dataStore.exts[extName];
              if(!extData) return;
       
              let usersArr = Object.entries(extData.extUsers).map(([phone, d]) => {
                  return { phone, name: dataStore.names[phone] || "לא ידוע", count: d.count, sec: d.sec };
              });
       
              usersArr.sort((a,b) => b.sec - a.sec);
       
              document.querySelector('#extModalTable tbody').innerHTML = usersArr.map(u => `
                  <tr>
                      <td style="color:var(--warning)">${u.name}</td>
                      <td style="font-weight:bold">${u.phone}</td>
                      <td>${u.count}</td>
                      <td style="color:var(--primary); font-weight:bold">${(u.sec/60).toFixed(1)} דק'</td>
                  </tr>
              `).join('');
       
              document.getElementById('extModal').style.display = 'flex';
          }
       
          function closeExtModal() { document.getElementById('extModal').style.display = 'none'; }
       
          function switchTab(e, id) {
              document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
              document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
              document.getElementById(id).classList.remove('hidden');
              e.currentTarget.classList.add('active');
          }
          
          function updateChart() {
              if (dailyChart) dailyChart.destroy();
              const ctx = document.getElementById('dailyChart').getContext('2d');
              const sortedDates = Object.keys(dataStore.daily).sort();
              const sortedData = sortedDates.map(d => dataStore.daily[d]);
       
              dailyChart = new Chart(ctx, {
                  type: 'line',
                  data: { labels: sortedDates, datasets: [{ label: 'פעולות במערכת', data: sortedData, borderColor: '#38bdf8', tension: 0.4, fill: true, backgroundColor: 'rgba(56, 189, 248, 0.1)' }] },
                  options: { plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } }
              });
          }
       
          let dEnd = new Date();
          let dStart = new Date();
          dStart.setDate(dEnd.getDate() - 7);
          document.getElementById('endDate').value = dEnd.toISOString().split('T')[0];
          document.getElementById('startDate').value = dStart.toISOString().split('T')[0];
      </script>
      </body>
      </html>
      
      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: תגובה לקבצי HTML לשימוש במערכות ימות המשיח

      @אופיר תודה רבה

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: מוזיקת רקע לתפריט

      @הקו-המוביל וכן בזה

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: מוזיקת רקע לתפריט

      @הקו-המוביל סליחה שאני מקפיץ, אבל תסתכל באילו:

        %D7%A4%D7%AA%D7%99%D7%97-%D7%94%D7%97%D7%93%D7%A9%D7%95%D7%AA-%D7%9E%D7%91%D7%96%D7%A7-%D7%9E%D7%A1%D7%9A-%D7%99%D7%A8%D7%95%D7%A7.mp3

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: קו עם כל הקווים

      @יענקי-פולק תודה רבה

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: שידור חי אינטרנטי תקלה

      @שואל-שאלה תגדיר כך:

      type=nitoviya
      nitoviya_dial_to=1700111010
      title=ר' מיילך
      
      
      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: שידור חי אינטרנטי תקלה

      לפני חצי שעה העלית שידור חי וזזה טלפון של הצינתוק של מידע כשר ולא של המערכת עצמה

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: שידור חי אינטרנטי תקלה

      מקפיץ.
      זה עדיין לא עובד.

      פורסם בשאלות ועזרה הדדית
      ע
      עובד את השם
    • RE: תגובות | פיתוח פרטי | לייק ודיסלייק בהשמעת קבצים 👍/👎

      מישהו יודע למה לא עובד לי?

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: תגובה לקבצי HTML לשימוש במערכות ימות המשיח

      @אופיר אתה יכול להעלות את זה כקובץ HTML?

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: תגובה לקבצי HTML לשימוש במערכות ימות המשיח

      @אופיר הוא העלה את זה ואני מכניס לפנקס רשימות ושומר כHTML וזה פותח אבל לא יודע למה זה לא עובד והקישורים שהוא הביא עבדו לי אתה יכול לנסות?

      <!DOCTYPE html>
      <html lang="he" dir="rtl">
      <head>
         <meta charset="UTF-8">
         <title>הורדת שלוחות למחשב</title>
         <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
         <style>
             body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 20px; background-color: #f4f7f6; text-align: right; direction: rtl; }
             .container { max-width: 1100px; margin: auto; background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
             .section { border: 1px solid #e0e0e0; padding: 15px; margin-bottom: 15px; border-radius: 8px; background: #fafafa; }
             #logArea { 
                 background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; 
                 height: 250px; overflow-y: auto; font-family: 'Consolas', monospace; font-size: 13px; margin-top: 10px;
             }
             .log-info { color: #4fc3f7; }
             .log-success { color: #00ff00; font-weight: bold; }
             .log-error { color: #ff5252; }
             button { padding: 10px 20px; background: #3498db; color: white; border: none; cursor: pointer; border-radius: 5px; font-weight: bold; transition: background 0.3s; }
             button:hover { background: #2980b9; }
             button:disabled { background: #bdc3c7; }
             input { padding: 10px; margin: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
             .token-input { width: 350px; }
             .config-input { width: 60px; text-align: center; }
             table { width: 100%; border-collapse: collapse; margin-top: 15px; }
             th, td { border: 1px solid #ddd; padding: 12px; text-align: right; }
             .progress-container { background: #e0e0e0; border-radius: 20px; height: 25px; margin: 15px 0; overflow: hidden; display: none; }
             .progress-bar { width: 0%; height: 100%; background: linear-gradient(90deg, #2ecc71, #27ae60); transition: width 0.3s; color: white; text-align: center; line-height: 25px; font-weight: bold; }
         </style>
      </head>
      <body>
      
      <div class="container">
         <h2>הורדת מבנה שלוחות למחשב (ZIP)</h2>
         
         <div class="section">
             <strong>⚙️ הגדרות:</strong>
             סרוק שלוחות בעלות <input type="number" id="digitCount" class="config-input" value="2" min="1" max="4"> ספרות.
             שם קובץ לשמירה: <input type="text" id="fileNameInput" placeholder="backup_ivr" value="backup_ivr">
         </div>
      
         <div class="section">
             <strong>1. פרטי המערכת</strong><br>
             <input type="text" id="srcToken" class="token-input" placeholder="הכנס טוקן מקור...">
             <input type="text" id="srcPath" class="path-input" placeholder="נתיב (למשל /)" value="/">
             <button onclick="loadFolder(document.getElementById('srcPath').value)">טען רשימת קבצים</button>
         </div>
      
         <div class="section">
             <strong>2. ביצוע הורדה</strong><br>
             <button id="downloadBtn" onclick="startDownload()" disabled style="background:#27ae60;">בחר מיקום והורד ZIP</button>
             
             <div class="progress-container" id="progContainer">
                 <div id="progBar" class="progress-bar">0%</div>
             </div>
             <div id="logArea">מוכן...</div>
         </div>
      
         <div id="fileArea" style="display:none;">
             <table id="fileTable">
                 <thead>
                     <tr>
                         <th style="width: 40px;"><input type="checkbox" id="masterCheck" checked onclick="toggleAll(this)"></th>
                         <th>סוג</th>
                         <th>שם</th>
                     </tr>
                 </thead>
                 <tbody id="fileTableBody"></tbody>
             </table>
         </div>
      </div>
      
      <script>
      let currentViewPath = "/";
      let zip = new JSZip();
      
      function addLog(msg, type = '') {
         const logArea = document.getElementById('logArea');
         const div = document.createElement('div');
         div.className = `log-${type}`;
         div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
         logArea.appendChild(div);
         logArea.scrollTop = logArea.scrollHeight;
      }
      
      async function loadFolder(path) {
         const token = document.getElementById('srcToken').value;
         const digits = parseInt(document.getElementById('digitCount').value) || 2;
         const maxRange = Math.pow(10, digits) - 1;
         if (!token) { alert("נא להזין טוקן"); return; }
      
         currentViewPath = path;
         const tbody = document.getElementById('fileTableBody');
         tbody.innerHTML = '<tr><td colspan="3">סורק...</td></tr>';
      
         try {
             const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${path}`);
             const data = await res.json();
             let filesMap = new Map();
      
             if (data.files) data.files.forEach(f => filesMap.set(f.name, f));
      
             const scanPromises = [];
             for (let i = 0; i <= maxRange; i++) {
                 const n = i.toString();
                 if (filesMap.has(n)) continue;
                 scanPromises.push(
                     fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${path}/${n}/ext.ini`)
                     .then(r => r.json())
                     .then(d => { if (d.contents !== undefined) filesMap.set(n, { name: n, fileType: "DIR" }); })
                     .catch(() => {})
                 );
             }
             await Promise.all(scanPromises);
      
             tbody.innerHTML = '';
             filesMap.forEach(f => {
                 tbody.insertAdjacentHTML('beforeend', `
                     <tr>
                         <td><input type="checkbox" class="file-check" data-name="${f.name}" data-type="${f.fileType}" checked></td>
                         <td>${f.fileType === "DIR" || !isNaN(f.name) ? "שלוחה" : "קובץ"}</td>
                         <td>${f.name}</td>
                     </tr>`);
             });
      
             document.getElementById('fileArea').style.display = 'block';
             document.getElementById('downloadBtn').disabled = false;
             addLog("סריקה הושלמה.", "info");
         } catch (e) { addLog("שגיאה בסריקה", "error"); }
      }
      
      async function downloadRecursive(token, path, name, type, zipFolder) {
         const sPath = `${path}/${name}`.replace(/\/+/g, '/');
         const digits = parseInt(document.getElementById('digitCount').value) || 2;
      
         if (type === "DIR" || !isNaN(name)) {
             addLog(`מוריד שלוחה: ${sPath}`, "info");
             const newFolder = zipFolder.folder(name);
             
             try {
                 const iniRes = await fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/ext.ini`);
                 const iniData = await iniRes.json();
                 if (iniData.contents !== undefined) newFolder.file("ext.ini.txt", iniData.contents);
             } catch(e) {}
      
             const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${token}&path=${sPath}`);
             const data = await res.json();
             let children = data.files || [];
      
             const subScan = [];
             for(let i=0; i <= Math.pow(10, digits)-1; i++) {
                 const n = i.toString();
                 if(!children.find(c => c.name === n)) {
                     subScan.push(
                         fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${token}&what=ivr2:${sPath}/${n}/ext.ini`)
                         .then(r => r.json())
                         .then(d => { if(d.contents !== undefined) children.push({name: n, fileType: "DIR"}); })
                     );
                 }
             }
             await Promise.all(subScan);
      
             for (const f of children) {
                 if (f.name === "ext.ini") continue;
                 await downloadRecursive(token, sPath, f.name, f.fileType, newFolder);
             }
         } else {
             try {
                 addLog(`מוריד קובץ: ${sPath}`, "info");
                 let finalName = name;
                 const lowerName = name.toLowerCase();
                 const isAudio = lowerName.endsWith('.mp3') || lowerName.endsWith('.wav');
                 
                 if (!isAudio && !lowerName.endsWith('.txt')) {
                     finalName = name + ".txt";
                 }
      
                 const dl = await fetch(`https://www.call2all.co.il/ym/api/DownloadFile?token=${token}&path=ivr2:${sPath}`);
                 const blob = await dl.blob();
                 zipFolder.file(finalName, blob);
             } catch(e) { addLog(`שגיאה בהורדת קובץ ${sPath}`, "error"); }
         }
      }
      
      async function startDownload() {
         const token = document.getElementById('srcToken').value;
         const userFileName = document.getElementById('fileNameInput').value || 'backup_ivr';
         const selected = Array.from(document.querySelectorAll('.file-check:checked'));
         
         if (selected.length === 0) { alert("לא נבחרו קבצים להורדה"); return; }
      
         // בקשת מיקום שמירה מהמשתמש לפני תחילת העבודה (בדפדפנים תומכים)
         let fileHandle = null;
         try {
             if ('showSaveFilePicker' in window) {
                 fileHandle = await window.showSaveFilePicker({
                     suggestedName: `${userFileName}.zip`,
                     types: [{
                         description: 'ZIP Archive',
                         accept: {'application/zip': ['.zip']},
                     }],
                 });
             }
         } catch (err) {
             if (err.name === 'AbortError') return; // המשתמש ביטל את חלונית השמירה
             addLog("דפדפן לא תומך בבחירת מיקום מראש, ההורדה תתבצע כרגיל בסיום.", "info");
         }
      
         zip = new JSZip(); 
         document.getElementById('downloadBtn').disabled = true;
         document.getElementById('progContainer').style.display = 'block';
      
         for (let i = 0; i < selected.length; i++) {
             const name = selected[i].getAttribute('data-name');
             const type = selected[i].getAttribute('data-type');
             await downloadRecursive(token, currentViewPath, name, type, zip);
             let p = Math.round(((i + 1) / selected.length) * 100);
             document.getElementById('progBar').style.width = p + '%';
             document.getElementById('progBar').innerText = p + '%';
         }
      
         addLog("מכין קובץ ZIP סופי...", "info");
         const content = await zip.generateAsync({type:"blob"});
      
         if (fileHandle) {
             // שמירה למיקום שהמשתמש בחר מראש
             const writable = await fileHandle.createWritable();
             await writable.write(content);
             await writable.close();
         } else {
             // הורדה רגילה לתיקיית ההורדות
             const link = document.createElement('a');
             link.href = URL.createObjectURL(content);
             link.download = `${userFileName}.zip`;
             link.click();
         }
         
         addLog("✅ ההורדה והשמירה הסתיימו בהצלחה!", "success");
         document.getElementById('downloadBtn').disabled = false;
      }
      
      function toggleAll(source) {
         document.querySelectorAll('.file-check').forEach(cb => cb.checked = source.checked);
      }
      </script>
      </body>
      </html>
      
      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם
    • RE: תגובה לקבצי HTML לשימוש במערכות ימות המשיח

      @אופיר תראה בזה

      פורסם בעזרה הדדית למשתמשים מתקדמים
      ע
      עובד את השם