PHP WebShell

Текущая директория: /var/www/bitcardoApp/backyard/user/giftcards

Просмотр файла: catalog.php

<?php
// backyard/user/giftcards/catalog.php
include '../common/header.php';

if (!isset($conn)) {
    include_once '../../config/db_config.php';
}
require_once '../../models/giftcards/catalog.php';

date_default_timezone_set('Africa/Lagos');

function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }

// Inputs
$brand_id = isset($_GET['brand']) ? (int)$_GET['brand'] : 0;
$page     = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 25;

// Data
$brands = gc_brands_all($conn);
$cards  = gc_cards_by_brand($conn, $brand_id ?: null, $page, $per_page);

// Keep qs
function qurl(array $add=[]){
  $qs = array_merge($_GET, $add);
  return '?' . http_build_query($qs);
}
?>
<style>
/* Mobile-first layout */
.brand-chip{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border:1px solid #eee;border-radius:.5rem;background:#fff;}
.brand-chip.active{border-color:#0d6efd;background:#eef5ff;}
.badge-dot{display:inline-block;width:.5rem;height:.5rem;border-radius:50%;margin-right:.25rem;}
.badge-dot.on{background:#28a745;}
.badge-dot.off{background:#dc3545;}
.table td, .table th{vertical-align:middle;}
</style>

<div class="nk-content nk-content-fluid mt-5">
  <div class="container-xl wide-lg">
    <div class="nk-content-body">

      <div class="nk-block-head">
        <div class="nk-block-between-md g-3 align-items-center">
          <div class="nk-block-head-content">
            <h4 class="nk-block-title fw-normal mb-1">Gift Card Catalog & Brand Manager</h4>
            <p class="text-soft mb-0">Manage brands and their denominations (buy/sell prices & status).</p>
          </div>
          <div class="nk-block-head-content d-flex gap-2">
            <button class="btn btn-primary btn-sm p-2" id="btnAddBrand"><i class="fa fa-plus"></i> Add Brand</button>
            <button class="btn btn-outline-primary btn-sm p-2" id="btnAddCard" <?= $brand_id? '' : 'disabled'; ?>><i class="fa fa-plus"></i> Add Denomination</button>
          </div>
        </div>
      </div>

      <div class="nk-block">
        <div class="row g-2">
          <!-- Brands column -->
          <div class="col-12 col-md-4">
            <div class="card card-bordered">
              <div class="card-inner">
                <div class="d-flex justify-content-between align-items-center mb-2">
                  <h6 class="mb-0">Brands</h6>
                  <span class="small text-soft"><?= count($brands); ?> total</span>
                </div>

                <?php if (empty($brands)): ?>
                  <div class="alert alert-light border mb-0">No brands yet.</div>
                <?php else: ?>
                  <div class="gy-2">
                    <?php foreach ($brands as $b): 
                      $active = ($brand_id === (int)$b['cbrand_id']);
                      $statusOn = ((int)$b['status'] === 1);
                    ?>
                      <div class="d-flex justify-content-between align-items-center mb-2 brand-chip <?= $active?'active':''; ?>">
                        <a href="<?= qurl(['brand'=>$b['cbrand_id'], 'page'=>1]); ?>" class="text-decoration-none text-dark d-flex align-items-center">
                          <span class="badge-dot <?= $statusOn?'on':'off'; ?>"></span>
                          <strong><?= h($b['card_brand']); ?></strong>
                        </a>
                        <div class="d-flex align-items-center gap-2">
                          <div class="form-check form-switch m-0">
                            <input class="form-check-input brandToggle" type="checkbox"
                                   data-id="<?= (int)$b['cbrand_id']; ?>" <?= $statusOn?'checked':''; ?>>
                          </div>
                          <button class="btn btn-xs btn-outline-secondary editBrandBtn"
                                  data-id="<?= (int)$b['cbrand_id']; ?>"
                                  data-name="<?= h($b['card_brand']); ?>"
                                  data-icon="<?= h($b['brand_icon'] ?? ''); ?>">
                            Edit
                          </button>
                        </div>
                      </div>
                    <?php endforeach; ?>
                  </div>
                <?php endif; ?>
              </div>
            </div>
          </div>

          <!-- Denoms column -->
          <div class="col-12 col-md-8">
            <div class="card card-bordered">
              <div class="card-inner">
                <div class="d-flex justify-content-between align-items-center mb-2">
                  <h6 class="mb-0">Denominations <?= $brand_id? 'for selected brand' : '(select a brand)'; ?></h6>
                  <?php if ($brand_id): ?>
                  <div class="small text-soft">Page <?= $cards['page']; ?> / <?= $cards['pages']; ?> • <?= number_format($cards['total']); ?> total</div>
                  <?php endif; ?>
                </div>

                <?php if (!$brand_id): ?>
                  <div class="alert alert-info mb-0">Select a brand on the left to view and manage denominations.</div>
                <?php else: ?>
                  <?php if (empty($cards['rows'])): ?>
                    <div class="alert alert-light border mb-0">No denominations yet for this brand.</div>
                  <?php else: ?>
                    <div class="table-responsive">
                      <table class="table table-striped">
                        <thead class="small text-soft">
                          <tr>
                            <th>Denom</th>
                            <th>Curr</th>
                            <th class="text-end">Buy Price</th>
                            <th class="text-end">Sell Price</th>
                            <th>Status</th>
                            <th class="text-end">Updated</th>
                            <th>Actions</th>
                          </tr>
                        </thead>
                        <tbody>
                          <?php foreach ($cards['rows'] as $r): 
                            $on = ((int)$r['status'] === 1);
                          ?>
                            <tr data-id="<?= (int)$r['gc_id']; ?>">
                              <td class="fw-semibold"><?= h($r['demon']); ?></td>
                              <td><?= h($r['card_curr']); ?></td>
                              <td class="text-end"><?= number_format((float)$r['buy_price'],2); ?></td>
                              <td class="text-end"><?= number_format((float)$r['sell_price'],2); ?></td>
                              <td><span class="badge <?= $on?'bg-success':'bg-danger'; ?>"><?= $on?'Active':'Inactive'; ?></span></td>
                              <td class="text-end small"><?= h($r['updated_at']); ?></td>
                              <td class="small">
                                <button class="btn btn-xs btn-outline-secondary editCardBtn"
                                        data-id="<?= (int)$r['gc_id']; ?>"
                                        data-brand="<?= (int)$r['cbrand_id']; ?>"
                                        data-demon="<?= h($r['demon']); ?>"
                                        data-curr="<?= h($r['card_curr']); ?>"
                                        data-buy="<?= h($r['buy_price']); ?>"
                                        data-sell="<?= h($r['sell_price']); ?>"
                                        data-status="<?= (int)$r['status']; ?>">
                                  Edit
                                </button>
                                <div class="form-check form-switch d-inline-block align-middle ms-2">
                                  <input class="form-check-input cardToggle" type="checkbox" data-id="<?= (int)$r['gc_id']; ?>" <?= $on?'checked':''; ?>>
                                </div>
                                <button class="btn btn-xs btn-outline-danger ms-1 delCardBtn" data-id="<?= (int)$r['gc_id']; ?>">Del</button>
                              </td>
                            </tr>
                          <?php endforeach; ?>
                        </tbody>
                      </table>
                    </div>

                    <!-- Pagination (simple) -->
                    <?php if ($cards['pages'] > 1): 
                      $p = $cards['page']; $pages=$cards['pages']; $prev=max(1,$p-1); $next=min($pages,$p+1);
                    ?>
                      <div class="d-flex justify-content-between align-items-center mt-2">
                        <div class="small text-soft">Page <?= $p; ?> of <?= $pages; ?></div>
                        <div class="d-flex gap-2">
                          <a class="btn btn-sm btn-outline-secondary <?= $p<=1?'disabled':''; ?>" href="<?= qurl(['brand'=>$brand_id,'page'=>$prev]); ?>">‹ Prev</a>
                          <a class="btn btn-sm btn-outline-secondary <?= $p>=$pages?'disabled':''; ?>" href="<?= qurl(['brand'=>$brand_id,'page'=>$next]); ?>">Next ›</a>
                        </div>
                      </div>
                    <?php endif; ?>
                  <?php endif; ?>
                <?php endif; ?>
              </div>
            </div>
          </div>
        </div><!-- row -->
      </div>

    </div>
  </div>
</div>

<!-- Brand Modal -->
<div class="modal fade" id="brandModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <form id="brandForm">
        <div class="modal-header">
          <h6 class="modal-title" id="brandModalTitle">Add Brand</h6>
          <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
        </div>
        <div class="modal-body">
          <input type="hidden" name="cbrand_id" id="b_id" value="">
          <div class="mb-2">
            <label class="form-label small">Brand Name</label>
            <input type="text" class="form-control" name="card_brand" id="b_name" required>
          </div>
          <div>
            <label class="form-label small">Icon (optional URL / filename)</label>
            <input type="text" class="form-control" name="brand_icon" id="b_icon" placeholder="/assets/icons/apple.png">
          </div>
          <div class="small text-soft mt-2">Tip: You can store icons anywhere public; this field is just a reference.</div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-primary" id="brandSaveBtn" type="submit">Save</button>
          <button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">Close</button>
        </div>
      </form>
    </div>
  </div>
</div>

<!-- Card Modal -->
<div class="modal fade" id="cardModal" tabindex="-1">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <form id="cardForm">
        <div class="modal-header">
          <h6 class="modal-title" id="cardModalTitle">Add Denomination</h6>
          <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
        </div>
        <div class="modal-body">
          <input type="hidden" name="gc_id" id="c_id" value="">
          <div class="mb-2">
            <label class="form-label small">Brand</label>
            <select class="form-select" name="cbrand_id" id="c_brand" required>
              <option value="">Select brand</option>
              <?php foreach ($brands as $b): ?>
                <option value="<?= (int)$b['cbrand_id']; ?>" <?= ($brand_id===(int)$b['cbrand_id'])?'selected':''; ?>>
                  <?= h($b['card_brand']); ?>
                </option>
              <?php endforeach; ?>
            </select>
          </div>
          <div class="row">
            <div class="mb-2 col-2">
                <label class="form-label small">Curr.</label>
                <input type="text" class="form-control" name="card_curr" id="c_curr" maxlength="1" placeholder="$" required>
            </div>
            <div class="mb-2 col-10">
                <label class="form-label small">Denomination (e.g. USA 50 Cash Receipt)</label>
                <input type="text" class="form-control" name="demon" id="c_demon" required>
            </div>
          </div>
          <div class="row g-2">
            <div class="col-6">
              <label class="form-label small">Buy Price (₦)</label>
              <input type="number" step="0.01" class="form-control" name="buy_price" id="c_buy">
            </div>
            <div class="col-6">
              <label class="form-label small">Sell Price (₦)</label>
              <input type="number" step="0.01" class="form-control" name="sell_price" id="c_sell" disabled>
            </div>
          </div>
          <div class="mt-2 form-check form-switch">
            <input class="form-check-input" type="checkbox" id="c_status" checked>
            <label class="form-check-label small" for="c_status">Active</label>
          </div>
          <div class="small text-soft mt-2">Note: `card_curr` is limited to 1 character by your schema; use `$` for USD, etc.</div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-primary" id="cardSaveBtn" type="submit">Save</button>
          <button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">Close</button>
        </div>
      </form>
    </div>
  </div>
</div>

<script>
(function(){
  const postUrl = '../../models/giftcards/catalog_post.php';

  // ---------- Brands ----------
  const brandModal = document.getElementById('brandModal');
  const brandForm  = document.getElementById('brandForm');
  const btnAddBrand= document.getElementById('btnAddBrand');
  const brandTitle = document.getElementById('brandModalTitle');
  const b_id = document.getElementById('b_id');
  const b_name = document.getElementById('b_name');
  const b_icon = document.getElementById('b_icon');

  btnAddBrand.addEventListener('click', ()=>{
    b_id.value = '';
    b_name.value = '';
    b_icon.value = '';
    brandTitle.textContent = 'Add Brand';
    bootstrap.Modal.getOrCreateInstance(brandModal).show();
  });

  document.querySelectorAll('.editBrandBtn').forEach(btn=>{
    btn.addEventListener('click', ()=>{
      b_id.value = btn.getAttribute('data-id') || '';
      b_name.value = btn.getAttribute('data-name') || '';
      b_icon.value = btn.getAttribute('data-icon') || '';
      brandTitle.textContent = 'Edit Brand';
      bootstrap.Modal.getOrCreateInstance(brandModal).show();
    });
  });

  brandForm.addEventListener('submit', async function(e){
    e.preventDefault();
    const id = b_id.value.trim();
    const payload = {
      action: id? 'brand.update' : 'brand.create',
      cbrand_id: id ? Number(id) : undefined,
      card_brand: b_name.value.trim(),
      brand_icon: b_icon.value.trim() || null
    };
    const r = await fetch(postUrl, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload)});
    const t = await r.text(); let data;
    try{ data = JSON.parse(t); }catch{ alert('Bad response: '+t.substring(0,200)); return; }
    if (!data.success){ alert(data.message || 'Failed to save brand'); return; }
    location.href = location.pathname + location.search; // keep current selection
  });

  document.querySelectorAll('.brandToggle').forEach(ch=>{
    ch.addEventListener('change', async ()=>{
      const id = Number(ch.getAttribute('data-id'));
      const status = ch.checked ? 1 : 0;
      const r = await fetch(postUrl,{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'brand.toggle', cbrand_id:id, status})});
      const t = await r.text(); let data;
      try{ data = JSON.parse(t); }catch{ alert('Toggle failed: '+t.substring(0,200)); return; }
      if(!data.success){ alert('Failed to toggle brand'); ch.checked = !ch.checked; return; }
      // stay
      location.href = location.pathname + location.search;
    });
  });

  // ---------- Cards ----------
  const cardModal = document.getElementById('cardModal');
  const cardForm  = document.getElementById('cardForm');
  const btnAddCard= document.getElementById('btnAddCard');
  const cardTitle = document.getElementById('cardModalTitle');
  const c_id   = document.getElementById('c_id');
  const c_brand= document.getElementById('c_brand');
  const c_demon= document.getElementById('c_demon');
  const c_curr = document.getElementById('c_curr');
  const c_buy  = document.getElementById('c_buy');
  const c_sell = document.getElementById('c_sell');
  const c_status = document.getElementById('c_status');

  btnAddCard.addEventListener('click', ()=>{
    c_id.value=''; c_demon.value=''; c_curr.value='$'; c_buy.value=''; c_sell.value=''; c_status.checked=true;
    cardTitle.textContent='Add Denomination';
    bootstrap.Modal.getOrCreateInstance(cardModal).show();
  });

  document.querySelectorAll('.editCardBtn').forEach(btn=>{
    btn.addEventListener('click', ()=>{
      c_id.value = btn.getAttribute('data-id') || '';
      c_brand.value = btn.getAttribute('data-brand') || '';
      c_demon.value = btn.getAttribute('data-demon') || '';
      c_curr.value  = btn.getAttribute('data-curr') || '$';
      c_buy.value   = btn.getAttribute('data-buy') || '';
      c_sell.value  = btn.getAttribute('data-sell') || '';
      c_status.checked = (btn.getAttribute('data-status')==='1');
      cardTitle.textContent='Edit Denomination';
      bootstrap.Modal.getOrCreateInstance(cardModal).show();
    });
  });

  cardForm.addEventListener('submit', async function(e){
    e.preventDefault();
    const id = c_id.value.trim();
    const payload = {
      action: id ? 'card.update' : 'card.create',
      gc_id: id ? Number(id) : undefined,
      cbrand_id: Number(c_brand.value),
      demon: c_demon.value.trim(),
      card_curr: c_curr.value.trim(),
      buy_price: c_buy.value===''? null : Number(c_buy.value),
      sell_price: c_sell.value===''? null : Number(c_sell.value),
      status: c_status.checked ? 1 : 0
    };
    const r = await fetch(postUrl,{method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)});
    const t = await r.text(); let data;
    try{ data = JSON.parse(t); }catch{ alert('Bad response: '+t.substring(0,200)); return; }
    if(!data.success){ alert(data.message || 'Failed to save denomination'); return; }
    // stay on brand list
    const params = new URLSearchParams(window.location.search);
    if (!params.has('brand')) params.set('brand', String(payload.cbrand_id));
    window.location.href = location.pathname + '?' + params.toString();
  });

  document.querySelectorAll('.cardToggle').forEach(ch=>{
    ch.addEventListener('change', async ()=>{
      const id = Number(ch.getAttribute('data-id'));
      const status = ch.checked ? 1 : 0;
      const r = await fetch(postUrl,{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'card.toggle', gc_id:id, status})});
      const t = await r.text(); let data;
      try{ data = JSON.parse(t); }catch{ alert('Toggle failed: '+t.substring(0,200)); return; }
      if(!data.success){ alert('Failed to toggle card'); ch.checked=!ch.checked; }
    });
  });

  document.querySelectorAll('.delCardBtn').forEach(btn=>{
    btn.addEventListener('click', async ()=>{
      const id = Number(btn.getAttribute('data-id'));
      if (!confirm('Delete this denomination?')) return;
      const r = await fetch(postUrl,{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'card.delete', gc_id:id})});
      const t = await r.text(); let data;
      try{ data = JSON.parse(t); }catch{ alert('Delete failed: '+t.substring(0,200)); return; }
      if(!data.success){ alert('Failed to delete card'); return; }
      location.reload();
    });
  });

})();
</script>

<?php include '../common/footer.php'; ?>

Выполнить команду


Для локальной разработки. Не используйте в интернете!