נתן & אורלי
מתחתנים

אנו שמחים ונרגשים להזמין אתכם לחגוג איתנו

04.09.25

קבלת פנים

19:30

חופה וקידושין

20:30

אישור הגעה

נשמח לראותכם בין אורחינו

מעקב מספרי דלק

מעקב מספרי דלק

כל מספר דלק שווה 100 ₪. כאן תוכל לעקוב אחרי כמה נשאר בכל מספר.
הוספת מספרי דלק חדשים
מספרי דלק (כל שורה = מספר)
אפשר להדביק כמה מספרים אחד מתחת לשני, כל שורה תישמר כמספר נפרד.
סטטוס כללי
💳 מספרי דלק פעילים: 0 💰 סה"כ יתרה: 0 ⛽ סה"כ שומש: 0
רשימת מספרי הדלק
				
					<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
  <meta charset="UTF-8" />
  <title>מעקב מספרי דלק</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>
    * { box-sizing: border-box; }
    body {
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      margin: 0;
      padding: 0;
      background: #f5f5f7;
      color: #111827;
    }
    .app {
      max-width: 960px;
      margin: 0 auto;
      padding: 24px 16px 48px;
    }
    h1 {
      font-size: 1.8rem;
      margin-bottom: 4px;
    }
    .subtitle {
      color: #6b7280;
      margin-bottom: 20px;
      font-size: 0.95rem;
    }
    .card {
      background: #ffffff;
      border-radius: 16px;
      padding: 16px 18px;
      margin-bottom: 16px;
      box-shadow: 0 10px 25px rgba(15, 23, 42, 0.06);
    }
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 12px;
      margin-bottom: 10px;
    }
    .label {
      font-size: 0.9rem;
      font-weight: 500;
      margin-bottom: 4px;
    }
    textarea,
    input[type="number"] {
      width: 100%;
      padding: 8px 10px;
      border-radius: 10px;
      border: 1px solid #d1d5db;
      font-size: 0.95rem;
      outline: none;
      transition: border-color 0.15s ease, box-shadow 0.15s ease;
      direction: ltr;
    }
    textarea:focus,
    input[type="number"]:focus {
      border-color: #2563eb;
      box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.15);
    }
    button {
      border-radius: 999px;
      border: none;
      padding: 8px 14px;
      font-size: 0.9rem;
      font-weight: 500;
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: 6px;
      transition: background 0.15s ease, transform 0.05s ease, box-shadow 0.15s ease;
      white-space: nowrap;
    }
    button:active {
      transform: translateY(1px);
      box-shadow: none;
    }
    .btn-primary {
      background: #2563eb;
      color: #ffffff;
      box-shadow: 0 8px 18px rgba(37, 99, 235, 0.35);
    }
    .btn-primary:hover { background: #1d4ed8; }
    .btn-outline {
      background: #ffffff;
      color: #374151;
      border: 1px solid #d1d5db;
    }
    .btn-outline:hover { background: #f3f4f6; }
    .btn-danger {
      background: #f97373;
      color: #fff;
    }
    .btn-danger:hover { background: #ef4444; }
    .btn-sm {
      padding: 5px 10px;
      font-size: 0.8rem;
    }
    .row {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
      align-items: flex-end;
    }
    .col {
      flex: 1;
      min-width: 160px;
    }
    .actions {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
    }
    .stats {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
      font-size: 0.9rem;
      color: #374151;
    }
    .badge {
      background: #eef2ff;
      color: #3730a3;
      border-radius: 999px;
      padding: 4px 12px;
      font-size: 0.8rem;
      display: inline-flex;
      align-items: center;
      gap: 6px;
    }
    .badge-strong {
      background: #dcfce7;
      color: #166534;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      font-size: 0.9rem;
    }
    thead { background: #f3f4f6; }
    th, td {
      padding: 8px 6px;
      border-bottom: 1px solid #e5e7eb;
      vertical-align: middle;
      text-align: right;
    }
    th {
      font-weight: 600;
      color: #4b5563;
    }
    tbody tr:last-child td { border-bottom: none; }
    tbody tr.used-up td {
      color: #9ca3af;
      background: #f9fafb;
    }
    .amount {
      font-variant-numeric: tabular-nums;
    }
    .pill {
      border-radius: 999px;
      padding: 2px 8px;
      font-size: 0.75rem;
      display: inline-block;
    }
    .pill-ok {
      background: #dcfce7;
      color: #166534;
    }
    .pill-empty {
      background: #fee2e2;
      color: #b91c1c;
    }
    .no-cards {
      padding: 12px 4px 4px;
      font-size: 0.9rem;
      color: #6b7280;
    }
    .error {
      color: #b91c1c;
      font-size: 0.85rem;
      margin-top: 4px;
    }
    .small-input { max-width: 120px; }
    @media (max-width: 640px) {
      .card-header {
        flex-direction: column;
        align-items: flex-start;
      }
      th, td { font-size: 0.8rem; }
      .actions { gap: 4px; }
    }
  </style>
</head>
<body>
  <div class="app">
    <h1>מעקב מספרי דלק</h1>
    <div class="subtitle">
      כל מספר דלק שווה <strong>100 ₪</strong>. כאן תוכל לעקוב אחרי כמה נשאר בכל מספר.
    </div>

    <div class="card" id="add-card">
      <div class="card-header">
        <div class="label">הוספת מספרי דלק חדשים</div>
      </div>
      <div class="row">
        <div class="col">
          <div class="label">מספרי דלק (כל שורה = מספר)</div>
          <textarea id="newCode" rows="3" placeholder="1234-5678-ABCD&#10;9999-8888-7777"></textarea>
          <div id="newCodeError" class="error" style="display:none"></div>
        </div>
        <div class="col" style="flex:0 0 auto; min-width:170px;">
          <button class="btn-primary" id="addCardBtn">
            ➕ הוסף מספרים (100 ₪ כל אחד)
          </button>
        </div>
      </div>
      <div style="margin-top:10px; font-size:0.85rem; color:#6b7280;">
        אפשר להדביק כמה מספרים אחד מתחת לשני, כל שורה תישמר כמספר נפרד.
      </div>
    </div>

    <div class="card" id="stats-card">
      <div class="card-header">
        <div class="label">סטטוס כללי</div>
      </div>
      <div class="stats">
        <span class="badge badge-strong">
          💳 מספרי דלק פעילים: <span id="statsCount">0</span>
        </span>
        <span class="badge">
          💰 סה"כ יתרה: <span id="statsTotalRemaining">0</span> ₪
        </span>
        <span class="badge">
          ⛽ סה"כ שומש: <span id="statsTotalUsed">0</span> ₪
        </span>
      </div>
    </div>

    <div class="card" id="list-card">
      <div class="card-header">
        <div class="label">רשימת מספרי הדלק</div>
        <div class="actions">
          <button class="btn-outline btn-sm" id="resetAllBtn">איפוס כל המערכת</button>
        </div>
      </div>
      <div id="cardsContainer"></div>
    </div>
  </div>

  <script>
    // ==== Supabase config (optional) ====
    const SUPABASE_URL = 'https://alervamlmqciixpzeprc.supabase.co';
    const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFsZXJ2YW1sbXFjaWl4cHplcHJjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjUwMDc1ODUsImV4cCI6MjA4MDU4MzU4NX0.Hg1nZVHFT6SC5mHeBzhHRCVVX_1EOHBztgP-RZPzK00'; 

    const TABLE_NAME = 'fuel_cards';
    const PROFILE_ID_KEY = 'fuelProfileId_v1';
    const LOCAL_STORAGE_KEY = 'fuelCardsLocal_v1';

    function hasSupabase() {
      return SUPABASE_URL && SUPABASE_KEY;
    }

    function generateId() {
      return String(Date.now()) + '_' + Math.random().toString(16).slice(2);
    }

    function getProfileId() {
      let id = localStorage.getItem(PROFILE_ID_KEY);
      if (!id) {
        id = 'profile_' + generateId();
        localStorage.setItem(PROFILE_ID_KEY, id);
      }
      return id;
    }

    function formatAmount(value) {
      return Number(value).toFixed(0);
    }

    function escapeHtml(str) {
      return String(str)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    }

    // ===== Local storage =====
    function loadCardsLocal() {
      try {
        const raw = localStorage.getItem(LOCAL_STORAGE_KEY);
        if (!raw) return [];
        const parsed = JSON.parse(raw);
        if (!Array.isArray(parsed)) return [];
        return parsed.map(c => ({
          id: c.id || generateId(),
          code: String(c.code || '').trim(),
          remaining: typeof c.remaining === 'number' ? c.remaining : 100
        })).filter(c => c.code);
      } catch (e) {
        console.error('Failed to load local cards', e);
        return [];
      }
    }

    function saveCardsLocal(cards) {
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(cards));
    }

    // ===== Supabase (optional) =====
    async function loadCardsSupabase() {
      if (!hasSupabase()) return null;
      const profileId = getProfileId();
      try {
        const res = await fetch(
          `${SUPABASE_URL}/rest/v1/${TABLE_NAME}?profile_id=eq.${encodeURIComponent(profileId)}&select=*`,
          {
            headers: {
              apikey: SUPABASE_KEY,
              Authorization: `Bearer ${SUPABASE_KEY}`
            }
          }
        );
        if (!res.ok) {
          console.warn('Supabase load error:', await res.text());
          return null;
        }
        const data = await res.json();
        return data.map(row => ({
          id: row.id || generateId(),
          code: row.code || '',
          remaining: typeof row.remaining === 'number' ? row.remaining : 100
        }));
      } catch (e) {
        console.warn('Supabase load exception:', e);
        return null;
      }
    }

    async function saveCardsSupabase(cards) {
      if (!hasSupabase()) return false;
      const profileId = getProfileId();
      try {
        // delete all existing rows for this profile
        await fetch(
          `${SUPABASE_URL}/rest/v1/${TABLE_NAME}?profile_id=eq.${encodeURIComponent(profileId)}`,
          {
            method: 'DELETE',
            headers: {
              apikey: SUPABASE_KEY,
              Authorization: `Bearer ${SUPABASE_KEY}`,
              Prefer: 'return=minimal'
            }
          }
        );

        if (!cards.length) return true;

        const payload = cards.map(c => ({
          profile_id: profileId,
          code: c.code,
          remaining: c.remaining
        }));

        const res = await fetch(
          `${SUPABASE_URL}/rest/v1/${TABLE_NAME}`,
          {
            method: 'POST',
            headers: {
              apikey: SUPABASE_KEY,
              Authorization: `Bearer ${SUPABASE_KEY}`,
              'Content-Type': 'application/json',
              Prefer: 'return=minimal'
            },
            body: JSON.stringify(payload)
          }
        );

        if (!res.ok) {
          console.warn('Supabase save error:', await res.text());
          return false;
        }
        return true;
      } catch (e) {
        console.warn('Supabase save exception:', e);
        return false;
      }
    }

    // ===== Unified load/save =====
    async function loadCards() {
      const fromSupabase = await loadCardsSupabase();
      if (fromSupabase && Array.isArray(fromSupabase)) {
        saveCardsLocal(fromSupabase);
        return fromSupabase;
      }
      return loadCardsLocal();
    }

    async function saveCards(cards) {
      saveCardsLocal(cards);
      const ok = await saveCardsSupabase(cards);
      if (!ok && hasSupabase()) {
        console.warn('Cloud save failed, kept local only');
      }
    }

    // ===== UI =====
    async function render() {
      const cards = await loadCards();
      const container = document.getElementById('cardsContainer');

      if (!cards.length) {
        container.innerHTML =
          '<div class="no-cards">עדיין לא הוספת מספרי דלק. התחל בהוספה למעלה 👆</div>';
      } else {
        let html =
          '<div style="overflow-x:auto;"><table><thead><tr>' +
          '<th>מספר דלק</th>' +
          '<th>סטטוס</th>' +
          '<th>יתרה (₪)</th>' +
          '<th>שומש (₪)</th>' +
          '<th>תדלוק</th>' +
          '<th>פעולות</th>' +
          '</tr></thead><tbody>';

        cards.forEach(card => {
          const used = 100 - (card.remaining || 0);
          const isEmpty = card.remaining <= 0;
          html +=
            '<tr class="' + (isEmpty ? 'used-up' : '') + '">' +
            '<td><strong>' + escapeHtml(card.code) + '</strong></td>' +
            '<td>' +
            (isEmpty
              ? '<span class="pill pill-empty">נוצל במלואו</span>'
              : '<span class="pill pill-ok">פעיל</span>') +
            '</td>' +
            '<td class="amount">' + formatAmount(Math.max(card.remaining, 0)) + '</td>' +
            '<td class="amount">' + formatAmount(Math.min(Math.max(used, 0), 100)) + '</td>' +
            '<td>' +
            '<div class="row" style="gap:6px; align-items:center;">' +
            '<div class="col" style="min-width:110px; flex:1;">' +
            '<input type="number" class="small-input" min="0" step="1" placeholder="סכום" ' +
            'data-type="refuel-input" data-id="' + card.id + '" />' +
            '</div>' +
            '<div style="flex:0 0 auto;">' +
            '<button class="btn-primary btn-sm" data-action="refuel" data-id="' + card.id + '">⛽ עדכן</button>' +
            '</div>' +
            '</div>' +
            '</td>' +
            '<td>' +
            '<button class="btn-outline btn-sm" data-action="reset-card" data-id="' + card.id + '">איפוס ל-100 ₪</button> ' +
            '<button class="btn-danger btn-sm" data-action="delete" data-id="' + card.id + '">מחק</button>' +
            '</td>' +
            '</tr>';
        });

        html += '</tbody></table></div>';
        container.innerHTML = html;
      }

      const totalRemaining = cards.reduce((sum, c) => sum + (c.remaining || 0), 0);
      const totalUsed = cards.length * 100 - totalRemaining;
      document.getElementById('statsCount').textContent = cards.length;
      document.getElementById('statsTotalRemaining').textContent =
        formatAmount(Math.max(totalRemaining, 0));
      document.getElementById('statsTotalUsed').textContent =
        formatAmount(Math.max(totalUsed, 0));
    }

    async function addCardsFromInput() {
      const input = document.getElementById('newCode');
      const errorEl = document.getElementById('newCodeError');
      let text = input.value.trim();

      errorEl.style.display = 'none';
      errorEl.textContent = '';

      if (!text) {
        errorEl.textContent = 'נא להזין לפחות מספר דלק אחד.';
        errorEl.style.display = 'block';
        return;
      }

      const lines = text.split(/\r?\n/)
        .map(l => l.trim())
        .filter(Boolean);

      if (!lines.length) {
        errorEl.textContent = 'לא נמצאו מספרים תקינים להוספה.';
        errorEl.style.display = 'block';
        return;
      }

      let cards = await loadCards();
      const existingCodes = new Set(cards.map(c => c.code));

      const newCards = [];
      const duplicates = [];

      lines.forEach(code => {
        if (existingCodes.has(code)) {
          duplicates.push(code);
        } else {
          newCards.push({
            id: generateId(),
            code,
            remaining: 100
          });
          existingCodes.add(code);
        }
      });

      if (!newCards.length) {
        errorEl.textContent = 'כל המספרים שהזנת כבר קיימים במערכת.';
        errorEl.style.display = 'block';
        return;
      }

      cards = cards.concat(newCards);
      await saveCards(cards);
      await render();

      if (duplicates.length) {
        errorEl.textContent =
          'חלק מהמספרים לא נוספו כי כבר קיימים: ' + duplicates.join(', ');
        errorEl.style.display = 'block';
      }

      input.value = '';
    }

    async function resetAll() {
      if (!confirm('לאפס את כל המערכת? כל מספרי הדלק והיתרות יימחקו.')) return;
      await saveCards([]);
      await render();
    }

    async function handleTableClick(event) {
      const btn = event.target.closest('button[data-action]');
      if (!btn) return;

      const action = btn.getAttribute('data-action');
      const id = btn.getAttribute('data-id');
      if (!action || !id) return;

      let cards = await loadCards();
      const idx = cards.findIndex(c => c.id === id);
      if (idx === -1) return;

      if (action === 'delete') {
        if (!confirm('למחוק את מספר הדלק הזה?')) return;
        cards.splice(idx, 1);
        await saveCards(cards);
        await render();
      } else if (action === 'reset-card') {
        if (!confirm('לאפס את המספר הזה ל-100 ₪?')) return;
        cards[idx].remaining = 100;
        await saveCards(cards);
        await render();
      } else if (action === 'refuel') {
        const row = btn.closest('tr');
        const input = row.querySelector(
          'input[data-type="refuel-input"][data-id="' + id + '"]'
        );
        if (!input) return;
        const raw = input.value.trim();
        const amount = Number(raw.replace(',', '.'));

        if (!raw || isNaN(amount) || amount <= 0) {
          alert('נא להזין סכום תדלוק גדול מ-0');
          return;
        }

        const card = cards[idx];
        if (amount > card.remaining) {
          if (
            !confirm(
              'הסכום גבוה מהיתרה (' +
                formatAmount(card.remaining) +
                ' ₪). האם לחייב עד הסוף ולהשאיר את המספר ריק?'
            )
          ) {
            return;
          }
          card.remaining = 0;
        } else {
          card.remaining = Number((card.remaining - amount).toFixed(2));
        }

        cards[idx] = card;
        await saveCards(cards);
        await render();
      }
    }

    document.addEventListener('DOMContentLoaded', () => {
      render();
      document
        .getElementById('addCardBtn')
        .addEventListener('click', () => { addCardsFromInput(); });
      document
        .getElementById('newCode')
        .addEventListener('keydown', (e) => {
          if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
            addCardsFromInput();
          }
        });
      document
        .getElementById('resetAllBtn')
        .addEventListener('click', () => { resetAll(); });
      document
        .getElementById('cardsContainer')
        .addEventListener('click', (e) => { handleTableClick(e); });
    });
  </script>
</body>
</html>