השנה ההתרמה שלכם תראה אחרת!!!
-
@BEN-ZION
עדיין? -
@אA
אולי בעיה של נטפרי? -
@BEN-ZION
ממש לא.
בכל פעם שאתה פותח מופיע כך? -
@אA ניסיתי כמה פעמים
-
@BEN-ZION
מוזר מאוד.
הורדתי שוב ונפתח מעולה.
ניסית לפתוח בקןבץ html? -
@אA הורדתי את הקובץ EXE והפעלתי אותו
לקחתי את הקוד ויצרתי HTML אצלי הוא עובד רק התוכנה לא עובדת -
@BEN-ZION
לא ממש יודע מה לומר לך.
ניסית להוריד גם מפורום כאן? -
אולי אנסה לשלוח כשזה מקופל אבל זאת נראת בעיה במחשב שלך
-
יש מצב שאתה בונה גם משהו לניהול מכירות לפי מק"ט ?
-
@אa מי שרוצה כתבתי קוד PHP שמקבל וובהוק מנדרים ומוסיף למערכת בהערות צריך לכתוב את שם המתרים
<?php // הגדרות API $token = "{token}"; $api_url = "https://www.call2all.co.il/ym/api/"; // נתיבים $path_names = 'ivr2:Points/EnterIDValName.ini'; $path_total = 'ivr2:Points/points_total.ymgr'; $path_log = 'ivr2:Points/manual_log.ini'; // קבצי דיבוג מקומיים בשרת ה-PHP $debug_file = 'debug_webhook.log'; $unassigned_csv = 'unassigned_donations.csv'; // 1. קבלת הנתונים ושמירה לדיבוג ראשוני $json_data = file_get_contents('php://input'); $data = json_decode($json_data, true); // רישום כל כניסה לקובץ דיבוג כדי שלא יאבד כלום $timestamp_log = date("Y-m-d H:i:s"); file_put_contents($debug_file, "[$timestamp_log] Raw Data: " . $json_data . PHP_EOL, FILE_APPEND); if (!$data) die("No data received"); $comment_name = trim($data['Comments'] ?? ''); $amount = floatval($data['Amount'] ?? 0); function call_yemot($method, $path, $token, $contents = null) { global $api_url; $url = $api_url . $method . "?token=" . $token . "&what=" . $path; if ($contents !== null) $url .= "&contents=" . urlencode($contents); $res = file_get_contents($url); return json_decode($res, true); } // 2. חיפוש גמיש (Fuzzy Matching) $fundraiser_id = null; $best_score = -1; $res_names = call_yemot('GetTextFile', $path_names, $token); if (isset($res_names['contents']) && !empty($comment_name)) { $lines = explode("\n", $res_names['contents']); foreach ($lines as $line) { $parts = explode('=', $line); if (count($parts) === 2) { $current_id = trim($parts[0]); $current_name = trim($parts[1]); // חישוב דמיון similar_text($comment_name, $current_name, $percent); if ($percent > $best_score) { $best_score = $percent; $fundraiser_id = $current_id; } } } } // 3. ביצוע עדכון או שמירה ל-CSV במקרה של כישלון // הורדתי את הסף ל-50% בגלל תארים כמו "הרב" ו"שליט"א" if ($fundraiser_id && $best_score > 50) { // א. עדכון YMGR $res_total = call_yemot('GetTextFile', $path_total, $token); $t_lines = explode("\n", trim($res_total['contents'] ?? "")); $found = false; foreach ($t_lines as &$line) { if (strpos($line, "EnterId#$fundraiser_id") !== false) { $found = true; preg_match('/PointsTotalAll#([\d.]+)/', $line, $matches); $new_val = (isset($matches[1]) ? floatval($matches[1]) : 0) + $amount; $line = preg_replace('/PointsTotalAll#[\d.]+/', "PointsTotalAll#$new_val", $line); $line = preg_replace('/PointsTotal#[\d.]+/', "PointsTotal#$new_val", $line); } } if (!$found) $t_lines[] = "EnterId#{$fundraiser_id}%PointsTotalAll#{$amount}%PointsTotal#{$amount}"; call_yemot('UploadTextFile', $path_total, $token, implode("\n", $t_lines)); // ב. רישום ללוג ה-HTML $res_log = call_yemot('GetTextFile', $path_log, $token); $time_ms = round(microtime(true) * 1000); $time_hi = date("H:i"); $new_entry = "{$time_ms}|{$fundraiser_id}|{$amount}|{$time_hi}"; call_yemot('UploadTextFile', $path_log, $token, $new_entry . "\n" . ($res_log['contents'] ?? "")); file_put_contents($debug_file, "[$timestamp_log] SUCCESS: Matched $comment_name to ID $fundraiser_id ($best_score%)" . PHP_EOL, FILE_APPEND); echo "Success"; } else { // שמירה ל-CSV עבור תרומות שלא זוהו $is_new = !file_exists($unassigned_csv); $fp = fopen($unassigned_csv, 'a'); if ($is_new) { fputs($fp, chr(0xEF) . chr(0xBB) . chr(0xBF)); // BOM לעברית fputcsv($fp, ['תאריך', 'שם מהערה', 'סכום', 'טלפון', 'אחוז התאמה הכי קרוב']); } fputcsv($fp, [$timestamp_log, $comment_name, $amount, $data['Phone'] ?? '', $best_score . "%"]); fclose($fp); file_put_contents($debug_file, "[$timestamp_log] FAILED: No match for $comment_name (Best: $best_score%)" . PHP_EOL, FILE_APPEND); echo "Saved to CSV"; }מי שרוצה שידרגתי את שתי הקודים כך שיש גם יעד אישי לכל מתרים
מסך ניהול<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <title>מנהל קמפיין - Points Sync</title> <style> :root { --primary: #0ea5e9; --success: #22c55e; --bg: #0f172a; --card: #1e293b; --border: #334155; --gold: #f59e0b; } body { background: var(--bg); color: #f1f5f9; font-family: system-ui, sans-serif; margin: 0; padding: 20px; text-align: right; } .manual-entry-box { max-width: 600px; margin: 50px auto; background: var(--card); padding: 30px; border-radius: 20px; box-shadow: 0 10px 25px rgba(0,0,0,0.3); border: 1px solid var(--border); } h1 { text-align: center; color: var(--primary); } label { display: block; margin-top: 15px; font-weight: bold; } input { width: 100%; padding: 15px; margin: 10px 0; border-radius: 10px; border: 1px solid var(--border); background: #0f172a; color: white; font-size: 1.2rem; box-sizing: border-box; } .main-btn { background: var(--success); color: white; border: none; padding: 15px; border-radius: 10px; cursor: pointer; font-weight: bold; width: 100%; font-size: 1.1rem; margin-top: 20px; transition: 0.3s; } .main-btn:hover { filter: brightness(1.1); transform: translateY(-2px); } .settings-trigger { position: fixed; top: 20px; left: 20px; background: #334155; color: white; border: none; padding: 12px 20px; border-radius: 50px; cursor: pointer; font-weight: bold; display: flex; align-items: center; gap: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 1000; backdrop-filter: blur(5px); } .modal-content { background: var(--card); width: 90%; max-width: 800px; margin: 40px auto; border-radius: 20px; padding: 25px; border: 1px solid var(--border); position: relative; max-height: 85vh; overflow-y: auto; } .close-btn { position: absolute; top: 15px; right: 20px; font-size: 1.5rem; cursor: pointer; } .tabs { display: flex; gap: 5px; border-bottom: 2px solid var(--border); margin-bottom: 20px; } .tab-btn { background: none; border: none; color: #8b949e; padding: 10px 15px; cursor: pointer; font-size: 0.95rem; } .tab-btn.active { color: var(--primary); border-bottom: 2px solid var(--primary); font-weight: bold; } .tab-pane { display: none; } .tab-pane.active { display: block; } .log-list { background: #0f172a; padding: 10px; border-radius: 8px; max-height: 350px; overflow-y: auto; } .log-item { display: flex; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border); align-items: center; } .log-item label { display: flex; align-items: center; cursor: pointer; width: 100%; gap: 15px; } .badge { font-size: 0.75rem; padding: 3px 8px; border-radius: 5px; color: white; font-weight: bold; } .badge-manual { background: var(--primary); } .badge-phone { background: var(--success); } .status { margin-top: 15px; font-size: 0.9rem; text-align: center; font-weight: bold; } </style> </head> <body> <button class="settings-trigger" onclick="openModal()"><span>⚙️</span> הגדרות קמפיין</button> <div class="manual-entry-box"> <h1>💰 הזנת תרומה ידנית</h1> <label>מספר אישי של המתרים:</label> <input type="number" id="manualId" placeholder="למשל 215" autofocus> <label>סכום התרומה (₪):</label> <input type="number" id="manualAmount" placeholder="0.00"> <button class="main-btn" onclick="executeManualDonation()">✅ אשר והוסף למערכת</button> <div id="mainStatus" class="status"></div> </div> <div id="settingsModal" class="modal"> <div class="modal-content"> <span class="close-btn" onclick="closeModal()">✖️</span> <h2 style="border-bottom: 1px solid var(--border); padding-bottom: 10px;">⚙️ ניהול והגדרות</h2> <div class="tabs"> <button class="tab-btn active" onclick="switchTab(event, 'tabNames')">👥 שמות ויעדים</button> <button class="tab-btn" onclick="switchTab(event, 'tabGoal')">📈 יעד ותאריך</button> <button class="tab-btn" onclick="switchTab(event, 'tabTotals')">📊 עריכת סכומים</button> <button class="tab-btn" onclick="switchTab(event, 'tabLogs')">🗑️ מחיקת לוג</button> </div> <div id="tabNames" class="tab-pane active"> <div style="display:flex; gap:10px;"> <input type="text" id="editId" placeholder="מספר אישי"> <button onclick="findUser()" style="width:100px; background:var(--primary); border:none; border-radius:8px; color:white; cursor:pointer;">חפש</button> </div> <input type="text" id="editName" placeholder="שם המתרים"> <input type="number" id="editGoal" placeholder="יעד אישי (₪) - אופציונלי"> <button class="main-btn" style="background:var(--primary)" onclick="saveUser()">שמור פרטים ויעד</button> </div> <div id="tabGoal" class="tab-pane"> <label>יעד הקמפיין הכולל (₪):</label><input type="number" id="goalVal"> <label>תאריך ושעת סיום:</label><input type="datetime-local" id="dateVal"> <button class="main-btn" onclick="saveConfig()">🔄 עדכן יעד ותאריך</button> <hr style="border:0; border-top:1px solid var(--border); margin:25px 0;"> <label style="color:var(--gold)">מפתח אבטחה (Token) API:</label> <input type="password" id="tokenInput" placeholder="הכנס טוקן חדש"> <button class="main-btn" style="background:#6366f1" onclick="updateToken()">🔄 עדכן מפתח Token</button> </div> <div id="tabTotals" class="tab-pane"><div id="totalsList" class="log-list">טוען נתונים...</div></div> <div id="tabLogs" class="tab-pane"> <div id="logsList" class="log-list">טוען לוג פעולות מהשרת...</div> <button class="main-btn" style="background:#ef4444" onclick="deleteLogs()">❌ מחק פעולות וסנכרן שרת</button> </div> <div id="modalStatus" class="status"></div> </div> </div> <script> let CURRENT_TOKEN = localStorage.getItem('campaign_token') || 'YOUR_TOKEN_HERE'; let nameMap = {}; let goalMap = {}; // נתיבים בשרת - הכל בתיקיית Points const PATH_TOTAL = 'ivr2:Points/points_total.ymgr'; const PATH_PHONE_LOG = 'ivr2:Points/points_log.2026-02.ymgr'; const PATH_MANUAL_LOG = 'ivr2:Points/manual_log.ini'; const PATH_CONFIG = 'ivr2:Points/config.ini'; const PATH_NAMES = 'ivr2:Points/EnterIDValName.ini'; const PATH_GOALS = 'ivr2:Points/EnterIDGoal.ini'; document.getElementById('tokenInput').value = CURRENT_TOKEN; function updateToken() { const newToken = document.getElementById('tokenInput').value.trim(); if(!newToken) return alert("נא להזין טוקן"); CURRENT_TOKEN = newToken; localStorage.setItem('campaign_token', newToken); alert("הטוקן עודכן במחשב זה!"); location.reload(); } function openModal() { document.getElementById('settingsModal').style.display = 'block'; } function closeModal() { document.getElementById('settingsModal').style.display = 'none'; } async function callApi(cmd, path, content = "") { let url = `https://www.call2all.co.il/ym/api/${cmd}?token=${CURRENT_TOKEN}&what=${path}`; if(content) url += `&contents=${encodeURIComponent(content)}`; return fetch(url).then(r => r.json()); } async function switchTab(e, id) { document.querySelectorAll('.tab-pane, .tab-btn').forEach(el => el.classList.remove('active')); document.getElementById(id).classList.add('active'); e.currentTarget.classList.add('active'); await fetchNames(); if(id === 'tabGoal') loadCurrentConfig(); if(id === 'tabTotals') fetchTotals(); if(id === 'tabLogs') fetchLogs(); } async function fetchNames() { const [resNames, resGoals] = await Promise.all([ callApi('GetTextFile', PATH_NAMES), callApi('GetTextFile', PATH_GOALS) ]); nameMap = {}; goalMap = {}; if (resNames.contents) { resNames.contents.split(/[\r\n]+/).forEach(line => { const parts = line.split('='); if (parts.length === 2) nameMap[parts[0].trim()] = parts[1].trim(); }); } if (resGoals.contents) { resGoals.contents.split(/[\r\n]+/).forEach(line => { const parts = line.split('='); if (parts.length === 2) goalMap[parts[0].trim()] = parseFloat(parts[1].trim()); }); } } async function findUser() { const id = document.getElementById('editId').value; if (!id) return alert("נא להזין מספר אישי"); await fetchNames(); document.getElementById('editName').value = nameMap[id] || ""; document.getElementById('editGoal').value = goalMap[id] || ""; } async function saveUser() { const id = document.getElementById('editId').value; const name = document.getElementById('editName').value; const goal = document.getElementById('editGoal').value; if (!id) return alert("חובה להזין מספר אישי"); let [resNames, resGoals] = await Promise.all([ callApi('GetTextFile', PATH_NAMES), callApi('GetTextFile', PATH_GOALS) ]); let linesNames = (resNames.contents || "").split('\n').filter(l => l.trim() && !l.startsWith(`${id}=`)); if (name) linesNames.push(`${id}=${name}`); let linesGoals = (resGoals.contents || "").split('\n').filter(l => l.trim() && !l.startsWith(`${id}=`)); if (goal) linesGoals.push(`${id}=${goal}`); await Promise.all([ callApi('UploadTextFile', PATH_NAMES, linesNames.join('\n')), callApi('UploadTextFile', PATH_GOALS, linesGoals.join('\n')) ]); alert("פרטי המתרים והיעד האישי עודכנו בהצלחה!"); await fetchNames(); } async function executeManualDonation() { const id = document.getElementById('manualId').value; const amt = parseFloat(document.getElementById('manualAmount').value); if(!id || !amt) return alert("מלא מספר וסכום"); document.getElementById('mainStatus').innerText = "⏳ מעדכן שרת..."; let resT = await callApi('GetTextFile', PATH_TOTAL); let tLines = (resT.contents || "").trim().split('\n'); let found = false; let updatedT = tLines.map(l => { if(l.includes(`EnterId#${id}`)) { found = true; let old = parseFloat((l.match(/PointsTotalAll#([\d.]+)/) || [0,0])[1]); let sum = old + amt; return l.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${sum}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${sum}`); } return l; }); if(!found) updatedT.push(`EnterId#${id}%PointsTotalAll#${amt}%PointsTotal#${amt}`); let resL = await callApi('GetTextFile', PATH_MANUAL_LOG); let currentManualLog = resL.contents || ""; const timeStr = new Date().toLocaleTimeString('he-IL', {hour:'2-digit', minute:'2-digit'}); const newEntry = `${Date.now()}|${id}|${amt}|${timeStr}`; const updatedManualLog = newEntry + "\n" + currentManualLog; await Promise.all([ callApi('UploadTextFile', PATH_TOTAL, updatedT.join('\n')), callApi('UploadTextFile', PATH_MANUAL_LOG, updatedManualLog) ]); document.getElementById('mainStatus').innerText = "✅ נרשם וסונכרן לשרת!"; document.getElementById('manualAmount').value = ""; } async function fetchLogs() { let [resPhone, resManual] = await Promise.all([ callApi('GetTextFile', PATH_PHONE_LOG), callApi('GetTextFile', PATH_MANUAL_LOG) ]); let allEntries = []; const todayStr = new Date().toISOString().split('T')[0]; if (resPhone.contents) { resPhone.contents.trim().split('\n').forEach(l => { let id = (l.match(/id#(\d+)/) || [])[1]; let pts = (l.match(/Points#(\d+)/) || [])[1]; let timeM = (l.match(/(\d{2}:\d{2})/) || [""])[0]; if (id) allEntries.push({id, pts, time: timeM, type: 'phone', fullLine: l, ts: timeM ? new Date(`${todayStr}T${timeM}:00`).getTime() : 0}); }); } if (resManual.contents) { resManual.contents.trim().split('\n').forEach(l => { let [ts, id, amt, time] = l.split('|'); if (id) allEntries.push({id, pts: amt, time, type: 'manual', fullLine: l, ts: parseInt(ts)}); }); } allEntries.sort((a, b) => b.ts - a.ts); let html = allEntries.map(entry => { let name = nameMap[entry.id] || entry.id; return `<div class="log-item"><label><input type="checkbox" class="log-cb" data-id="${entry.id}" data-amt="${entry.pts}" data-type="${entry.type}" data-line="${entry.fullLine}"><span><span class="badge ${entry.type === 'manual' ? 'badge-manual' : 'badge-phone'}">${entry.type === 'manual' ? 'ידני' : 'טלפון'}</span> [${entry.time}] <b>${name}</b> - ₪${entry.pts}</span></label></div>`; }).join(''); document.getElementById('logsList').innerHTML = html || "אין היסטוריה."; } async function deleteLogs() { const checkboxes = document.querySelectorAll('.log-cb:checked'); if (checkboxes.length === 0) return alert("בחר פעולות"); if (!confirm("זה ימחק את השורות מכל המחשבים ויקזז סכומים. המשך?")) return; document.getElementById('modalStatus').innerText = "⏳ מסנכרן שרת..."; let [resP, resM, resT] = await Promise.all([ callApi('GetTextFile', PATH_PHONE_LOG), callApi('GetTextFile', PATH_MANUAL_LOG), callApi('GetTextFile', PATH_TOTAL) ]); let phoneLines = resP.contents ? resP.contents.trim().split('\n') : []; let manualLines = resM.contents ? resM.contents.trim().split('\n') : []; let totalLines = resT.contents ? resT.contents.trim().split('\n') : []; checkboxes.forEach(cb => { const id = cb.getAttribute('data-id'), amt = parseFloat(cb.getAttribute('data-amt')), type = cb.getAttribute('data-type'), line = cb.getAttribute('data-line'); if (type === 'phone') phoneLines = phoneLines.filter(l => l !== line); else manualLines = manualLines.filter(l => l !== line); totalLines = totalLines.map(tL => { if(tL.includes(`EnterId#${id}`)) { let old = parseFloat((tL.match(/PointsTotalAll#([\d.]+)/) || [0,0])[1]); let sum = Math.max(0, old - amt); return tL.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${sum}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${sum}`); } return tL; }); }); await Promise.all([ callApi('UploadTextFile', PATH_PHONE_LOG, phoneLines.join('\n')), callApi('UploadTextFile', PATH_MANUAL_LOG, manualLines.join('\n')), callApi('UploadTextFile', PATH_TOTAL, totalLines.join('\n')) ]); document.getElementById('modalStatus').innerText = "✅ הסנכרון הושלם בכל המחשבים!"; fetchLogs(); } async function loadCurrentConfig() { const res = await callApi('GetTextFile', PATH_CONFIG); if(res.contents) { const goal = (res.contents.match(/goal=(.*)/) || [])[1]; const date = (res.contents.match(/date=(.*)/) || [])[1]; if(goal) document.getElementById('goalVal').value = goal; if(date) document.getElementById('dateVal').value = date; } } async function saveConfig() { const content = `goal=${document.getElementById('goalVal').value}\ndate=${document.getElementById('dateVal').value}`; await callApi('UploadTextFile', PATH_CONFIG, content); alert("עודכן בשרת!"); } async function fetchTotals() { let res = await callApi('GetTextFile', PATH_TOTAL); if(!res.contents) return; let html = res.contents.trim().split('\n').map(l => { let id = (l.match(/EnterId#(\d+)/) || [])[1]; let val = (l.match(/PointsTotalAll#([\d.]+)/) || [])[1]; if(!id) return ''; let name = nameMap[id] || id; return `<div class="log-item"><span><b>${name}</b> (${id})</span><div style="display:flex; gap:5px;"><input type="number" id="inp-${id}" value="${val}" style="width:110px;"><button onclick="updateTotal('${id}')" style="background:var(--primary); color:white; border:none; padding:8px 12px; border-radius:8px; cursor:pointer;">עדכן</button></div></div>`; }).join(''); document.getElementById('totalsList').innerHTML = html; } async function updateTotal(id) { const newVal = document.getElementById(`inp-${id}`).value; let res = await callApi('GetTextFile', PATH_TOTAL); let updated = res.contents.trim().split('\n').map(l => l.includes(`EnterId#${id}`) ? l.replace(/PointsTotalAll#[\d.]+/, `PointsTotalAll#${newVal}`).replace(/PointsTotal#[\d.]+/, `PointsTotal#${newVal}`) : l); await callApi('UploadTextFile', PATH_TOTAL, updated.join('\n')); alert("סכום עודכן בשרת!"); } </script> </body> </html>מסך התרמה
<!DOCTYPE html> <html lang="he" dir="rtl"> <head> <meta charset="UTF-8"> <title>מערכת התרמה LIVE - Points Sync</title> <style> :root { --accent: #0ea5e9; --gold: #fbbf24; --bg: #020617; } body { background: var(--bg); color: #f8fafc; font-family: system-ui, sans-serif; margin: 0; padding: 10px; overflow: hidden; } .container { display: grid; grid-template-columns: 320px 1fr 320px; gap: 15px; height: 95vh; } .card { background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255,255,255,0.1); border-radius: 20px; padding: 15px; backdrop-filter: blur(10px); display: flex; flex-direction: column; overflow: hidden; } .center-card { display: flex; flex-direction: column; justify-content: space-between; align-items: center; text-align: center; padding: 40px 20px; } h2 { font-size: 1.3rem; border-bottom: 2px solid var(--accent); padding-bottom: 8px; margin: 0 0 10px 0; color: white; text-align: center; } .countdown-timer { display: flex; gap: 15px; margin-bottom: 20px; } .time-unit { background: rgba(14, 165, 233, 0.2); padding: 10px 15px; border-radius: 12px; border: 1px solid var(--accent); min-width: 60px; } .time-num { display: block; font-size: 2rem; font-weight: bold; color: white; } .time-label { font-size: 0.8rem; opacity: 0.7; } .total-amount { font-size: 8rem; font-weight: 900; color: var(--accent); text-shadow: 0 0 30px rgba(14, 165, 233, 0.4); margin: 10px 0; line-height: 1; } .progress-wrapper { width: 90%; } .progress-bg { background: #1e293b; height: 40px; border-radius: 50px; border: 2px solid #334155; overflow: hidden; position: relative; } .progress-fill { height: 100%; background: linear-gradient(90deg, #0ea5e9, #22d3ee); width: 0%; transition: width 2s; } .list-box { flex-grow: 1; overflow: hidden; display: flex; flex-direction: column; padding-right: 5px; } .item { display: flex; flex-direction: column; align-items: stretch; padding: 10px; background: rgba(255,255,255,0.05); margin-bottom: 8px; border-radius: 10px; border-right: 4px solid var(--accent); flex-shrink: 0; transition: all 0.5s; } .item-header { display: flex; justify-content: space-between; align-items: center; } .item-info { display: flex; flex-direction: column; } .item-name { font-weight: bold; font-size: 1.1rem; display: flex; align-items: center; gap: 6px; } .item-time { font-size: 0.8rem; color: #94a3b8; font-weight: bold; } .item-val { font-weight: 900; color: var(--gold); font-size: 1.3rem; } .badge { font-size: 0.6rem; padding: 2px 5px; border-radius: 4px; font-weight: bold; } .badge-manual { background: #0ea5e9; color: white; } .badge-phone { background: #22c55e; color: white; } .live-dot { height: 10px; width: 10px; background: #ef4444; border-radius: 50%; display: inline-block; margin-left: 8px; animation: pulse 1s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } /* עיצוב לאנימציה של הגעה ליעד */ @keyframes goldGlow { 0% { box-shadow: 0 0 5px rgba(251, 191, 36, 0.2); border-color: rgba(251, 191, 36, 0.5); } 50% { box-shadow: 0 0 20px rgba(251, 191, 36, 0.8); border-color: rgba(251, 191, 36, 1); } 100% { box-shadow: 0 0 5px rgba(251, 191, 36, 0.2); border-color: rgba(251, 191, 36, 0.5); } } .goal-reached { animation: goldGlow 2s infinite; background: rgba(251, 191, 36, 0.1) !important; border-right-color: var(--gold) !important; } .goal-badge { background: var(--gold); color: black; font-size: 0.75rem; padding: 2px 8px; border-radius: 12px; font-weight: bold; margin-right: 10px; display: inline-block; } </style> </head> <body> <div class="container"> <div class="card"> <h2>🏆 מובילים</h2> <div id="leaderboard" class="list-box" style="overflow-y: auto;"></div> </div> <div class="card center-card"> <div> <div style="margin-bottom: 10px; font-size: 1.2rem; font-weight: bold;"><span class="live-dot"></span>הקמפיין מסתיים בעוד:</div> <div id="countdown" class="countdown-timer"> <div class="time-unit"><span id="days" class="time-num">00</span><span class="time-label">ימים</span></div> <div class="time-unit"><span id="hours" class="time-num">00</span><span class="time-label">שעות</span></div> <div class="time-unit"><span id="minutes" class="time-num">00</span><span class="time-label">דקות</span></div> <div class="time-unit"><span id="seconds" class="time-num">00</span><span class="time-label">שניות</span></div> </div> </div> <div> <h1 style="margin:0; font-size: 1.8rem; opacity: 0.9;">סך הכל נאסף:</h1> <div id="main-total" class="total-amount">₪0</div> </div> <div class="progress-wrapper"> <div class="progress-bg"><div id="progress-bar" class="progress-fill"></div></div> <div id="percent-label" style="font-size: 3rem; color: var(--gold); font-weight: bold; margin-top: 10px;">0%</div> <div id="goal-text" style="font-size: 1.5rem; opacity: 0.6;">מחשב נתונים...</div> </div> </div> <div class="card"> <h2>🔔 תרומות אחרונות</h2> <div id="recent-log" class="list-box" style="overflow-y: auto;"></div> </div> </div> <script> // הגדרות שלוחה - הכל תחת תיקיית Points const TOKEN = 'WU1BUElL.apik_MmtjfW7MhNRZGw4h--R54Q.zPUwopxRh5JxRpUutkWFKUhyDWtLfvYwWKQ30PTuXZw'; const PATH_TOTAL = 'ivr2:Points/points_total.ymgr'; const PATH_PHONE_LOG = 'ivr2:Points/points_log.2026-02.ymgr'; const PATH_MANUAL_LOG = 'ivr2:Points/manual_log.ini'; const PATH_CONFIG = 'ivr2:Points/config.ini'; const PATH_NAMES = 'ivr2:Points/EnterIDValName.ini'; const PATH_GOALS = 'ivr2:Points/EnterIDGoal.ini'; let GOAL = 500000; let DEADLINE = new Date(); let nameMap = {}; let goalMap = {}; function updateCountdown() { const now = new Date(); const diff = DEADLINE - now; if (diff <= 0) { document.querySelectorAll('.time-num').forEach(el => el.innerText = "00"); return; } document.getElementById('days').innerText = String(Math.floor(diff / (1000 * 60 * 60 * 24))).padStart(2, '0'); document.getElementById('hours').innerText = String(Math.floor((diff / (1000 * 60 * 60)) % 24)).padStart(2, '0'); document.getElementById('minutes').innerText = String(Math.floor((diff / 1000 / 60) % 60)).padStart(2, '0'); document.getElementById('seconds').innerText = String(Math.floor((diff / 1000) % 60)).padStart(2, '0'); } async function update() { try { const [resTotal, resLogPhone, resNames, resConfig, resLogManual, resGoals] = await Promise.all([ fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_TOTAL}`).then(r => r.json()), fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_PHONE_LOG}`).then(r => r.json()), fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_NAMES}`).then(r => r.json()), fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_CONFIG}`).then(r => r.json()), fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_MANUAL_LOG}`).then(r => r.json()), fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${TOKEN}&what=${PATH_GOALS}`).then(r => r.json()) ]); if (resConfig.contents) { const g = resConfig.contents.match(/goal=(.*)/); const d = resConfig.contents.match(/date=(.*)/); if (g) { GOAL = parseFloat(g[1]); document.getElementById('goal-text').innerText = `מתוך יעד של ${GOAL.toLocaleString()} ₪`; } if (d) DEADLINE = new Date(d[1]); } if (resNames.contents) { resNames.contents.split(/[\r\n]+/).forEach(line => { const parts = line.split('='); if (parts.length === 2) nameMap[parts[0].trim()] = parts[1].trim(); }); } if (resGoals.contents) { goalMap = {}; resGoals.contents.split(/[\r\n]+/).forEach(line => { const parts = line.split('='); if (parts.length === 2) goalMap[parts[0].trim()] = parseFloat(parts[1].trim()); }); } if (resTotal.contents) { const lines = resTotal.contents.trim().split(/[\r\n]+/); let totalSum = 0; let users = []; lines.forEach(l => { const idMatch = l.match(/EnterId#(\d+)/); const scoreMatch = l.match(/PointsTotalAll#([\d.]+)/); if (idMatch && scoreMatch) { const val = parseFloat(scoreMatch[1]); totalSum += val; users.push({id: idMatch[1], val: val}); } }); document.getElementById('main-total').innerText = '₪' + totalSum.toLocaleString(); document.getElementById('progress-bar').style.width = Math.min((totalSum/GOAL)*100, 100) + '%'; document.getElementById('percent-label').innerText = ((totalSum/GOAL)*100).toFixed(1) + '%'; users.sort((a,b) => b.val - a.val); document.getElementById('leaderboard').innerHTML = users.map((u,i) => { const personalGoal = goalMap[u.id]; let goalHtml = ''; let reachedClass = ''; let goalBadgeHtml = ''; if (personalGoal && personalGoal > 0) { const rawPct = (u.val / personalGoal) * 100; const pct = Math.min(rawPct, 100).toFixed(0); if (rawPct >= 100) { reachedClass = 'goal-reached'; goalBadgeHtml = `<span class="goal-badge">הגיע ליעד! 👑</span>`; } goalHtml = ` <div style="font-size: 0.8rem; color: #94a3b8; display: flex; align-items: center; gap: 8px; margin-top: 8px;"> <div style="background: rgba(255,255,255,0.1); height: 6px; flex-grow: 1; border-radius: 5px; overflow: hidden;"> <div style="background: var(--gold); height: 100%; width: ${pct}%; transition: width 1s;"></div> </div> <span style="min-width: 40px; text-align: left; font-weight: bold; color: ${rawPct >= 100 ? 'var(--gold)' : 'inherit'}">${pct}%</span> </div> `; } return ` <div class="item ${reachedClass}"> <div class="item-header"> <span class="item-name"><b>${i+1}.</b> ${nameMap[u.id] || u.id} ${goalBadgeHtml}</span> <span style="color:var(--accent); font-weight:bold;">₪${u.val.toLocaleString()}</span> </div> ${goalHtml} </div> `; }).join(''); } let allItems = []; const todayStr = new Date().toISOString().split('T')[0]; if (resLogPhone.contents) { resLogPhone.contents.trim().split(/[\r\n]+/).forEach((line, idx) => { const idM = line.match(/id#(\d+)/); const ptM = line.match(/Points#(\d+)/); const timeM = line.match(/(\d{2}:\d{2})/); if (idM && ptM) { let ts = timeM ? new Date(`${todayStr}T${timeM[1]}:00`).getTime() : (Date.now() - 500000); allItems.push({ id: idM[1], amount: ptM[1], type: 'phone', timestamp: ts, timeStr: timeM ? timeM[1] : "--:--" }); } }); } if (resLogManual.contents) { resLogManual.contents.trim().split('\n').forEach(l => { let [ts, id, amt, time] = l.split('|'); if (id) allItems.push({ id, amount: amt, type: 'manual', timestamp: parseInt(ts), timeStr: time }); }); } allItems.sort((a, b) => b.timestamp - a.timestamp); document.getElementById('recent-log').innerHTML = allItems.map(item => ` <div class="item" style="flex-direction: row; justify-content: space-between; align-items: center;"> <div class="item-info"> <span class="item-name"> <span class="badge ${item.type === 'manual' ? 'badge-manual' : 'badge-phone'}">${item.type === 'manual' ? 'ידני' : 'טלפון'}</span> ${nameMap[item.id] || 'מתרים ' + item.id} </span> <span class="item-time">${item.timeStr}</span> </div> <span class="item-val">₪${parseFloat(item.amount).toLocaleString()}</span> </div> `).join(''); } catch (e) { console.error("Update Error:", e); } } setInterval(updateCountdown, 1000); setInterval(update, 5000); updateCountdown(); update(); </script> </body> </html>קרדיט ל @אa על הפיתוח המועיל