PHP WebShell
Текущая директория: /var/www/bitcardoApp/backyard/user/giftcards
Просмотр файла: index.php
<?php
// backyard/user/giftcards/index.php
include '../common/header.php';
if (!isset($conn)) {
include_once '../../config/db_config.php';
}
require_once '../../models/giftcards/index.php';
date_default_timezone_set('Africa/Lagos');
// helpers
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function moneyx($n, $d=2){ return number_format((float)$n, $d); }
function badge_status($s){
$u = strtoupper((string)$s);
if (in_array($u, ['APPROVED','SUCCESS','COMPLETED'])) return 'bg-success';
if (in_array($u, ['PENDING','PROCESSING'])) return 'bg-warning';
if (in_array($u, ['DECLINED','FAILED','REJECTED'])) return 'bg-danger';
return 'bg-secondary';
}
// Read filters
$filters = [
'from' => $_GET['from'] ?? '',
'to' => $_GET['to'] ?? '',
'status' => $_GET['status'] ?? '',
'brand_id' => $_GET['brand_id'] ?? '',
'batch_ref' => $_GET['batch_ref'] ?? '',
'card_ref' => $_GET['card_ref'] ?? '',
'trade_ref' => $_GET['trade_ref'] ?? '',
'currency' => $_GET['currency'] ?? '',
'min_value' => $_GET['min_value'] ?? '',
'max_value' => $_GET['max_value'] ?? '',
'min_payout' => $_GET['min_payout'] ?? '',
'max_payout' => $_GET['max_payout'] ?? '',
'user_q' => $_GET['user_q'] ?? '',
'page' => (int)($_GET['page'] ?? 1),
'per_page' => (int)($_GET['per_page'] ?? 25),
];
$brands = gc_get_brands($conn);
$result = gc_search($conn, $filters);
// Keep query string for pagination
function qurl(array $add=[]){
$qs = array_merge($_GET, $add);
return '?' . http_build_query($qs);
}
?>
<style>
/* Mobile-first tweaks */
.gc-filter-toggle { display:inline-flex; align-items:center; gap:.4rem; }
.gc-sticky-actions { position: sticky; bottom: 0; background: #fff; padding: .5rem 0; }
.gc-thumb { width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:.5rem; }
.gc-noimg { height:120px; border-radius:.5rem; }
@media (min-width: 768px){
.gc-filter-collapse{ display:block !important; height:auto !important; }
}
</style>
<div class="nk-content nk-content-fluid">
<div class="container-xl wide-lg">
<div class="nk-content-body">
<div class="nk-block-head mt-5">
<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">All Gift Card Trades</h4>
<p class="text-soft mb-0">Filter, search and navigate all submitted gift card trades.</p>
</div>
<div class="nk-block-head-content">
<a href="../dashboard/index.php" class="btn btn-outline-secondary d-none d-md-inline-flex"><span>Back to Dashboard</span></a>
<a href="../dashboard/index.php" class="btn btn-outline-secondary d-md-none btn-sm"><span>Back</span></a>
</div>
</div>
</div>
<!-- Filters -->
<div class="nk-block">
<div class="card card-bordered">
<div class="card-inner">
<!-- Mobile: collapsible -->
<div class="d-flex justify-content-between align-items-center d-md-none mb-2">
<button class="btn btn-light btn-sm gc-filter-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#gcFilters">
<em class="icon ni ni-filter"></em><span>Show Filters</span>
</button>
</div>
<div id="gcFilters" class="collapse gc-filter-collapse">
<form class="row g-2" method="get">
<!-- Row 1 -->
<div class="col-6 col-md-2">
<label class="form-label small">From</label>
<input type="date" name="from" class="form-control" value="<?= h($filters['from']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">To</label>
<input type="date" name="to" class="form-control" value="<?= h($filters['to']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Status</label>
<select name="status" class="form-select">
<option value="">Any</option>
<?php foreach (['PENDING','APPROVED','DECLINED','SUCCESS','COMPLETED','FAILED','PROCESSING'] as $st): ?>
<option value="<?= h($st); ?>" <?= $filters['status']===$st?'selected':''; ?>><?= h($st); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Brand</label>
<select name="brand_id" class="form-select">
<option value="">Any</option>
<?php foreach ($brands as $b): ?>
<option value="<?= (int)$b['cbrand_id']; ?>" <?= ($filters['brand_id']==$b['cbrand_id']?'selected':''); ?>>
<?= h($b['card_brand']); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Batch No</label>
<input type="text" name="batch_ref" class="form-control" value="<?= h($filters['batch_ref']); ?>" placeholder="e.g. PK3737">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Card Ref</label>
<input type="text" name="card_ref" class="form-control" value="<?= h($filters['card_ref']); ?>">
</div>
<!-- Row 2 -->
<div class="col-6 col-md-2">
<label class="form-label small">Trade Ref</label>
<input type="text" name="trade_ref" class="form-control" value="<?= h($filters['trade_ref']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Currency</label>
<input type="text" name="currency" class="form-control" value="<?= h($filters['currency']); ?>" placeholder="USD, $">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Min Value</label>
<input type="number" step="0.01" name="min_value" class="form-control" value="<?= h($filters['min_value']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Max Value</label>
<input type="number" step="0.01" name="max_value" class="form-control" value="<?= h($filters['max_value']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Min Payout (₦)</label>
<input type="number" step="0.01" name="min_payout" class="form-control" value="<?= h($filters['min_payout']); ?>">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Max Payout (₦)</label>
<input type="number" step="0.01" name="max_payout" class="form-control" value="<?= h($filters['max_payout']); ?>">
</div>
<!-- Row 3 -->
<div class="col-12 col-md-3">
<label class="form-label small">User (name/email/phone)</label>
<input type="text" name="user_q" class="form-control" value="<?= h($filters['user_q']); ?>" placeholder="John, +234..., mail@...">
</div>
<div class="col-6 col-md-2">
<label class="form-label small">Per Page</label>
<select name="per_page" class="form-select">
<?php foreach ([25,50,100] as $pp): ?>
<option value="<?= $pp; ?>" <?= ($filters['per_page']==$pp?'selected':''); ?>><?= $pp; ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-7 d-flex align-items-end justify-content-end mt-1">
<a href="index.php" class="btn btn-outline-secondary me-2 w-50 w-md-auto">Reset</a>
<button class="btn btn-primary w-50 w-md-auto">Apply Filters</button>
</div>
</form>
</div><!-- /collapse -->
</div>
</div>
</div>
<!-- Results -->
<div class="nk-block">
<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">Results</h6>
<div class="small text-soft"><?= number_format($result['total']); ?> total</div>
</div>
<!-- Bulk toolbar -->
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center mb-2">
<div class="small text-soft" id="bulkCount">0 selected</div>
<div class="d-flex gap-2">
<button id="btnBulkPreview" class="btn btn-outline-info btn-sm p-2" disabled>Preview</button>
<button id="btnBulkApprove" class="btn btn-success btn-sm p-2" disabled>Approve Selected</button>
<button id="btnBulkDecline" class="btn btn-danger btn-sm p-2" disabled>Decline Selected</button>
</div>
</div>
<!-- Mobile: card list -->
<div class="d-md-none">
<?php if (empty($result['rows'])): ?>
<div class="alert alert-light border mb-0">No records found.</div>
<?php else: ?>
<div class="gy-2">
<?php foreach ($result['rows'] as $r):
$fullname = trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? ''));
$badge = badge_status($r['trade_status']);
$created = $r['trade_created'] ? date('M j, Y g:i A', strtotime($r['trade_created'])) : '—';
$valueDisp = moneyx($r['card_value'] ?? 0, 2) . ' ' . h($r['card_curr'] ?? '');
$payoutDisp = moneyx($r['est_payout_ngn'] ?? 0, 2);
?>
<div class="border rounded-3 p-3 mb-2" data-ref="<?= h($r['card_ref']); ?>">
<div class="d-flex justify-content-between align-items-start">
<div class="form-check m-0">
<input class="form-check-input rowCheck" type="checkbox">
</div>
<span class="badge <?= $badge; ?>"><?= h(ucfirst(strtolower($r['trade_status'] ?: '—'))); ?></span>
</div>
<div class="row mt-2 g-1">
<div class="col-6">
<div class="small text-soft">Batch No</div>
<a class="fw-semibold" href="../orders/process_order.php?batch=<?= urlencode($r['batch_ref']); ?>">
<?= h($r['batch_ref'] ?: '—'); ?>
</a>
</div>
<div class="col-6">
<div class="small text-soft">Card Ref</div>
<div class="fw-semibold">
<?= $r['card_ref'] ? '<a href="../orders/process_order.php?ref='.urlencode($r['card_ref']).'">'.h($r['card_ref']).'</a>' : '—'; ?>
</div>
</div>
<div class="col-6">
<div class="small text-soft">Brand</div>
<div class="fw-semibold"><?= h($r['card_brand'] ?: '—'); ?></div>
</div>
<div class="col-6">
<div class="small text-soft">Denom</div>
<div class="fw-semibold"><?= h($r['demon'] ?: '—'); ?></div>
</div>
<div class="col-6">
<div class="small text-soft">Value</div>
<div class="fw-semibold"><?= $valueDisp; ?></div>
</div>
<div class="col-6">
<div class="small text-soft">Est. Payout (₦)</div>
<div class="fw-semibold"><?= $payoutDisp; ?></div>
</div>
<div class="col-12">
<div class="small text-soft">User</div>
<div class="fw-semibold"><?= h($fullname ?: '—'); ?></div>
<div class="text-soft small"><?= h($r['email'] ?: $r['phone'] ?: ''); ?></div>
</div>
<div class="col-12">
<div class="small text-soft">Created</div>
<div class="fw-semibold small"><?= h($created); ?></div>
</div>
</div>
<div class="d-flex gap-2 mt-3 mb-2">
<a class="btn btn-sm btn-outline-primary w-50 py-2" href="../orders/process_order.php?ref=<?= urlencode($r['card_ref']); ?>">View Card</a>
<a class="btn btn-sm btn-outline-secondary w-50 py-2" href="../orders/process_order.php?batch=<?= urlencode($r['batch_ref']); ?>">View Batch</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Desktop: table -->
<div class="table-responsive d-none d-md-block mt-5">
<table class="table table-striped" id="gcTable">
<thead class="small text-soft">
<tr>
<th style="width:32px;"><input type="checkbox" id="checkAll"></th>
<th>Batch No</th>
<th>Card Ref</th>
<th>Brand</th>
<th>Denom</th>
<th>Value</th>
<th>Est. Payout (₦)</th>
<th>User</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($result['rows'])): ?>
<tr><td colspan="11" class="text-center text-muted py-4">No records found.</td></tr>
<?php else: ?>
<?php foreach ($result['rows'] as $r):
$fullname = trim(($r['first_name'] ?? '') . ' ' . ($r['last_name'] ?? ''));
$badge = badge_status($r['trade_status']);
$created = $r['trade_created'] ? date('M j, Y g:i A', strtotime($r['trade_created'])) : '—';
$valueDisp = moneyx($r['card_value'] ?? 0, 2) . ' ' . h($r['card_curr'] ?? '');
$payoutDisp = moneyx($r['est_payout_ngn'] ?? 0, 2);
?>
<tr data-ref="<?= h($r['card_ref']); ?>">
<td><input type="checkbox" class="rowCheck"></td>
<td><a href="../orders/process_order.php?batch=<?= urlencode($r['batch_ref']); ?>"><?= h($r['batch_ref'] ?: '—'); ?></a></td>
<td><?= $r['card_ref'] ? '<a href="../orders/process_order.php?ref='.urlencode($r['card_ref']).'">'.h($r['card_ref']).'</a>' : '—'; ?></td>
<td><?= h($r['card_brand'] ?: '—'); ?></td>
<td><?= h($r['demon'] ?: '—'); ?></td>
<td><?= $valueDisp; ?></td>
<td><?= $payoutDisp; ?></td>
<td>
<div class="small">
<div class="fw-semibold"><?= h($fullname ?: '—'); ?></div>
<div class="text-soft"><?= h($r['email'] ?: $r['phone'] ?: ''); ?></div>
</div>
</td>
<td><span class="badge <?= $badge; ?>"><?= h(ucfirst(strtolower($r['trade_status'] ?: '—'))); ?></span></td>
<td class="small"><?= h($created); ?></td>
<td class="small">
<a class="btn btn-xs btn-outline-primary me-1" href="../orders/process_order.php?ref=<?= urlencode($r['card_ref']); ?>"><i class="fa fa-eye"></i></a>
<a class="btn btn-xs btn-outline-secondary" href="../orders/process_order.php?batch=<?= urlencode($r['batch_ref']); ?>"><i class="fa fa-files-o"></i></a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php if ($result['pages'] > 1): ?>
<div class="mt-3">
<!-- Mobile pagination -->
<div class="d-md-none gc-sticky-actions">
<?php
$p = $result['page']; $pages = $result['pages'];
$prev = max(1, $p-1); $next = min($pages, $p+1);
?>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary w-50 <?= $p<=1?'disabled':''; ?>" href="<?= qurl(['page'=>$prev]); ?>">‹ Prev</a>
<a class="btn btn-outline-secondary w-50 <?= $p>=$pages?'disabled':''; ?>" href="<?= qurl(['page'=>$next]); ?>">Next ›</a>
</div>
<div class="small text-center text-soft mt-1">Page <?= $p; ?> of <?= $pages; ?></div>
</div>
<!-- Desktop pagination -->
<div class="d-none d-md-flex justify-content-between align-items-center">
<div class="small text-soft">Page <?= $result['page']; ?> of <?= $result['pages']; ?></div>
<ul class="pagination pagination-sm mb-0">
<?php
$p = $result['page']; $pages = $result['pages'];
$prev = max(1, $p-1); $next = min($pages, $p+1);
?>
<li class="page-item <?= $p<=1?'disabled':''; ?>">
<a class="page-link" href="<?= qurl(['page'=>1]); ?>">« First</a>
</li>
<li class="page-item <?= $p<=1?'disabled':''; ?>">
<a class="page-link" href="<?= qurl(['page'=>$prev]); ?>">‹ Prev</a>
</li>
<li class="page-item disabled"><span class="page-link"><?= $p; ?></span></li>
<li class="page-item <?= $p>=$pages?'disabled':''; ?>">
<a class="page-link" href="<?= qurl(['page'=>$next]); ?>">Next ›</a>
</li>
<li class="page-item <?= $p>=$pages?'disabled':''; ?>">
<a class="page-link" href="<?= qurl(['page'=>$pages]); ?>">Last »</a>
</li>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bulk Preview Modal -->
<div class="modal fade" id="bulkPreviewModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Preview Selected Cards</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="bulkPreviewBody" class="row g-2"></div>
</div>
<div class="modal-footer">
<button type="button" id="modalApprove" class="btn btn-success">Approve All</button>
<button type="button" id="modalDecline" class="btn btn-danger">Decline All</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
(function(){
const checkAll = document.getElementById('checkAll');
const bulkCount = document.getElementById('bulkCount');
const btnPrev = document.getElementById('btnBulkPreview');
const btnApp = document.getElementById('btnBulkApprove');
const btnDec = document.getElementById('btnBulkDecline');
const modal = document.getElementById('bulkPreviewModal');
const modalApprove = document.getElementById('modalApprove');
const modalDecline = document.getElementById('modalDecline');
const body = document.getElementById('bulkPreviewBody');
function selectedRefs(){
const refs = [];
document.querySelectorAll('tr[data-ref], .border[data-ref]').forEach(function(row){
const cb = row.querySelector('.rowCheck');
if (cb && cb.checked) refs.push(row.getAttribute('data-ref'));
});
return refs;
}
function setButtons(){
const cnt = selectedRefs().length;
bulkCount.textContent = cnt + ' selected';
const on = cnt > 0;
btnPrev.disabled = !on; btnApp.disabled = !on; btnDec.disabled = !on;
if (checkAll){
const rows = Array.from(document.querySelectorAll('tbody .rowCheck'));
checkAll.checked = rows.length && rows.every(cb=>cb.checked);
checkAll.indeterminate = rows.some(cb=>cb.checked) && !checkAll.checked;
}
}
document.addEventListener('change', function(e){
if (e.target.id === 'checkAll'){
const state = e.target.checked;
document.querySelectorAll('.rowCheck').forEach(cb => cb.checked = state);
setButtons();
} else if (e.target.classList.contains('rowCheck')){
setButtons();
}
});
async function fetchPreview(refs){
const resp = await fetch('../../models/giftcards/bulk_preview.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({refs})
});
const text = await resp.text();
let data;
try {
data = JSON.parse(text);
} catch (e) {
throw new Error('Preview parse error: ' + text.substring(0,150));
}
if (!data.success) throw new Error(data.message || 'Preview failed');
return data.items || [];
}
function cardHtml(it){
const img = it.thumb
? `<img src="${it.thumb}" class="gc-thumb">`
: `<div class="bg-light border d-flex align-items-center justify-content-center gc-noimg">No Image</div>`;
const val = (it.card_value ?? '') + ' ' + (it.card_curr ?? '');
const payout = (Number(it.est_payout_ngn||0)).toLocaleString(undefined,{minimumFractionDigits:2, maximumFractionDigits:2});
return `
<div class="col-12 col-md-6">
<div class="border rounded p-2 h-100">
${img}
<div class="mt-2 small"><strong>Card Ref:</strong> ${it.card_ref || '—'}</div>
<div class="small"><strong>Batch:</strong> ${it.batch_ref || '—'}</div>
<div class="small"><strong>Brand:</strong> ${it.card_brand || '—'}</div>
<div class="small"><strong>Denom:</strong> ${it.demon || '—'}</div>
<div class="small"><strong>Value:</strong> ${val}</div>
<div class="small"><strong>Est. Payout (₦):</strong> ${payout}</div>
<div class="small"><strong>Status:</strong> ${it.trade_status || '—'}</div>
</div>
</div>`;
}
document.getElementById('btnBulkPreview').addEventListener('click', async function(){
const refs = selectedRefs();
if (!refs.length) return;
body.innerHTML = '<div class="text-center text-soft py-3">Loading preview…</div>';
try{
const items = await fetchPreview(refs);
body.innerHTML = items.length ? items.map(cardHtml).join('') : '<div class="alert alert-light border mb-0">Nothing to preview.</div>';
bootstrap.Modal.getOrCreateInstance(modal).show();
}catch(err){
alert(err.message || 'Preview failed');
}
});
async function doBulk(action, refs){
const resp = await fetch('../../models/giftcards/bulk_actions_post.php', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({action, refs})
});
const text = await resp.text();
let data; try{ data = JSON.parse(text); }catch{ throw new Error('Bad JSON: '+text.substring(0,150)); }
if(!data || !('success' in data)){ throw new Error('Unexpected response'); }
const ok = (data.results||[]).filter(r=>r.ok).length;
const total = (data.results||[]).length;
alert(`${action.toUpperCase()}: ${ok}/${total} succeeded`);
location.reload();
}
document.getElementById('btnBulkApprove').addEventListener('click', ()=>{
const refs=selectedRefs(); if(!refs.length) return;
if(confirm('Approve selected cards?')) doBulk('approve', refs);
});
document.getElementById('btnBulkDecline').addEventListener('click', ()=>{
const refs=selectedRefs(); if(!refs.length) return;
if(confirm('Decline selected cards?')) doBulk('decline', refs);
});
document.getElementById('modalApprove').addEventListener('click', ()=>{
const refs=selectedRefs(); if(!refs.length) return;
doBulk('approve', refs);
});
document.getElementById('modalDecline').addEventListener('click', ()=>{
const refs=selectedRefs(); if(!refs.length) return;
doBulk('decline', refs);
});
// init counter
setButtons();
})();
</script>
<?php include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!