השנה ההתרמה שלכם תראה אחרת!!!
-
לקראת ימי הפורים המתקרבים, שמח להציג ממשק מיוחד שיוכל לעזור לכם מאוד בהתרמה וניהול החפ"ק שלכם.
שימו לב! זוהי גירסא ראשונית. בעז"ה אני מקווה לשפר אותה יחד עם הארות והערות של המשתמשים.
כמו כן יש לבצע הרצה לממשק לפי שימוש נרחב.הממשק מורכב כרגע משתי חלקים:
1. הממשק הויזואלי.
2.הממשק הטלפוני.1. הממשק הויזואלי:
הממשק כולל תוכנה אותה ניתן להקרין בחפ"ק ובה מתעדכנים הנתונים אונליין הן מהמערכת הטלפוני והן ממשק הניהול.
בממשק מופיעים התרומות האחרונות כולל שם המתרים ושעת ההתרמה, וכן 10 המתרימים המובילים, וכמובן ספירה לאחור עד לסיום הקמפיין ועידכון אונליין של הסכום שנאסף עד כה ואחוזי האסיפה.בנוסף יש את ממשק הניהול שהיא תוכנה נפרדת דרכה ניתן לעדכן בכל עת תרומות חדשות שהתקבלו והם יסונכרנו הן במסך והן במערכת הטלפונית.
כמו כן ניתן לנהל את הגדרות ההתרמה, תאריך ושעת הסיום, יעד ההתרמה, וכן את הפעולות האחרונות שהוכנסו והסכומים של כלל המתרימים.2.המערכת הטלפונית:
במערכת ניתן לעדכן את הסכום אותו אסף כל מתרים באמצעות מספר אישי (ת.ז. או מספר מיוחד), וכן לשמוע את הסכום הכולל שנאסף עד כה.
שימו לב! הסכומים מסתנכרנים יחד עם הממשק גם בטלפון!מה נצרך כדי לתפעל את ההתרמה?
1. מחשב עם חיבור אינטרנטי רציף.
2. חיבור אינטרנטי יציב ומהיר.
וזהו!שימו לב!
ניתן לעבוד על הקמפיין מכמה מחשבים בו זמנית וזה מכיוון שכל הנתונים גם הידנים נכנסים לקובץ במערכת הטלפונית ומשם הנתונים נלקחים!!!שימו לב שהטוקן אחיד כדי שכולם יוכלו לעבוד יחד!
וכעת להגדרות למערכת הטלפונית:
; הגדרת השלוחה כהוספת נקודות type=points_save ; שיתן לבחור את סכום הנקודות points_tfr_add=yes ; הזיהוי של תעודת זהות enter_id_type=teudat_zehut enter_id=yes ; שישמיע את השם בכניסה login_add_val_name=yes ; שלא יבקש להקליט את השם record_name=no ; שבכל כניסה לשלוחה יבקש זיהוי מחדש delete_id_exit=yes ; שיקח את השמות מתקיית הניקוד login_add_val_name_folder=/Pointsבנוסף בתקייה Points בשלוחה הראשית יש לפתוח 3 קבצי ini.
1. manual_log.ini
2. config.ini
3. EnterIDValNameכמובן שיש להחליף את ההקלטות הנצרכות בעז"ה הביא אותם בהמשך.
וכעת לממשקים:
1. מסך ההתרמהכתוכנה
https://mitmachim.top/topic/93250/מדריך-השנה-ההתרמה-שלכם-תראה-אחרתהקוד
<!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; } .item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: rgba(255,255,255,0.05); margin-bottom: 8px; border-radius: 10px; border-right: 4px solid var(--accent); flex-shrink: 0; } .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; } } </style></head>
<body><div class="container">
<div class="card">
<h2>
מובילים</h2>
<div id="leaderboard" class="list-box"></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"></div> </div></div>
<script>
// הגדרות שלוחה - הכל תחת תיקיית Points
const TOKEN = localStorage.getItem('campaign_token') || 'Token';
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';let GOAL = 500000; let DEADLINE = new Date(); let nameMap = {}; 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] = 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()) ]); // עדכון יעד ותאריך 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 (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)=>` <div class="item"> <span class="item-name"><b>${i+1}.</b> ${nameMap[u.id] || u.id}</span> <span style="color:var(--accent); font-weight:bold;">₪${u.val.toLocaleString()}</span> </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); // כאן הורדנו את ה-slice כדי שהריבוע יתמלא לגמרי document.getElementById('recent-log').innerHTML = allItems.map(item => ` <div class="item"> <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>
2. ניהול ההתרמה
כתוכנה
https://mitmachim.top/topic/93250/מדריך-השנה-ההתרמה-שלכם-תראה-אחרתהקוד
<!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="findName()" style="width:100px; background:var(--primary); border:none; border-radius:8px; color:white; cursor:pointer;">חפש</button></div> <input type="text" id="editName" placeholder="שם המתרים"> <button class="main-btn" style="background:var(--primary)" onclick="saveName()">שמור שם</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 = {};// נתיבים בשרת - הכל בתיקיית 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'; 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 res = await callApi('GetTextFile', PATH_NAMES); if (res.contents) { nameMap = {}; res.contents.split(/[\r\n]+/).forEach(line => { const parts = line.split('='); if (parts.length === 2) nameMap[parts[0].trim()] = parts[1].trim(); }); } } 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("סכום עודכן בשרת!"); } async function findName() { const id = document.getElementById('editId').value; await fetchNames(); document.getElementById('editName').value = nameMap[id] || ""; } async function saveName() { const id = document.getElementById('editId').value; const name = document.getElementById('editName').value; let res = await callApi('GetTextFile', PATH_NAMES); let lines = (res.contents || "").split('\n').filter(l => l.trim() && !l.startsWith(`${id}=`)); lines.push(`${id}=${name}`); await callApi('UploadTextFile', PATH_NAMES, lines.join('\n')); alert("שם עודכן בשרת!"); await fetchNames(); }</script>
</body>
</html>