PHP WebShell
Текущая директория: /var/www/bitcardoApp/backyard/user/orders
Просмотр файла: process_order.php
<?php
// backyard/user/orders/process_order.php
include '../common/header.php';
if (!isset($conn)) {
include_once '../../config/db_config.php';
}
require_once '../../models/dashboard/index.php';
date_default_timezone_set('Africa/Lagos');
// Absolute base for card images (from your working link)
$IMG_BASE = 'https://wallet.bitcardo.com/backyard/uploads/cards/';
// --------------------------------- helpers ---------------------------------
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function fmt_money($n, $dec=2){ return number_format((float)$n, $dec); }
function status_badge_class($status_raw){
$s = strtoupper(trim((string)$status_raw));
if (in_array($s, ['SUCCESS','COMPLETED','APPROVED'])) return 'bg-success';
if (in_array($s, ['PENDING','PROCESSING'])) return 'bg-warning';
if (in_array($s, ['DECLINED','FAILED','REJECTED'])) return 'bg-danger';
if ($s === 'MIXED') return 'bg-info';
return 'bg-secondary';
}
function safe_now(mysqli $conn){
$r = mysqli_query($conn, "SELECT NOW() as n");
$row = $r? mysqli_fetch_assoc($r):null;
if($r) mysqli_free_result($r);
return $row? $row['n'] : date('Y-m-d H:i:s');
}
/**
* Save uploaded evidence file for a trade into 'uploads/cards/<year>/<month>/<batch>/'
* Returns array on success or false on failure
*/
function save_evidence_upload(mysqli $conn, int $trade_id, string $batch_ref, string $file_field='evidence'){
if (!isset($_FILES[$file_field]) || !is_uploaded_file($_FILES[$file_field]['tmp_name'])) return false;
$f = $_FILES[$file_field];
if ($f['error'] !== UPLOAD_ERR_OK) return false;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $f['tmp_name']);
finfo_close($finfo);
if (!preg_match('#^image/(jpeg|png|gif|webp)$#', $mime)) return false;
// max 5MB
if ($f['size'] > 5 * 1024 * 1024) return false;
$year = date('Y');
$month = date('m');
$safeBatch = preg_replace('/[^A-Za-z0-9_-]/','_', $batch_ref ?: 'unknown');
// <-- corrected to year/month/batch
$baseDirRel = "uploads/cards/{$year}/{$month}/{$safeBatch}/";
$absBase = __DIR__ . '/../../' . $baseDirRel;
if (!is_dir($absBase)) {
if (!mkdir($absBase, 0755, true) && !is_dir($absBase)) {
return false;
}
}
$ext = pathinfo($f['name'], PATHINFO_EXTENSION);
$filename = strtoupper(bin2hex(random_bytes(6))) . ($ext ? ".{$ext}" : '');
$destAbs = $absBase . $filename;
// store relative path under uploads/cards/ (so other code that prefixes IMG_BASE still works)
$destRel = $year . '/' . $month . '/' . $safeBatch . '/' . $filename;
if (!move_uploaded_file($f['tmp_name'], $destAbs)) {
return false;
}
$trade_id_i = (int)$trade_id;
$path_safe = mysqli_real_escape_string($conn, $destRel);
$orig_safe = mysqli_real_escape_string($conn, $f['name']);
$mime_safe = mysqli_real_escape_string($conn, $mime);
$size_i = (int)$f['size'];
$q = "INSERT INTO card_trade_images (trade_id, path, original_name, mime, size, uploaded_at)
VALUES ({$trade_id_i}, '{$path_safe}', '{$orig_safe}', '{$mime_safe}', {$size_i}, NOW())";
if (!mysqli_query($conn, $q)) {
@unlink($destAbs);
return false;
}
return [
'path' => $destRel,
'original_name' => $f['name'],
'mime' => $mime,
'size' => $size_i,
];
}
// ---------------------------- POST: approve/decline -------------------------
// Uses card_ref (not trade_ref)
$flash = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'], $_POST['ref'])) {
$action = strtolower(trim($_POST['action']));
$cardRef = trim($_POST['ref']); // <- card_ref
$ref_safe = mysqli_real_escape_string($conn, $cardRef);
// Fetch card by card_ref
$sql = "SELECT trade_id, user_id, trade_status, est_payout_ngn, batch_ref
FROM card_trade WHERE card_ref = '{$ref_safe}' LIMIT 1";
$res = mysqli_query($conn, $sql);
$card = $res ? mysqli_fetch_assoc($res) : null;
if ($res) mysqli_free_result($res);
if (!$card) {
$flash = ['type'=>'danger','msg'=>"Card not found for card_ref: ".h($cardRef)];
} else {
$trade_id = (int)$card['trade_id'];
$user_id = (int)$card['user_id'];
$status = strtoupper(trim($card['trade_status'] ?? ''));
$payout = (float)($card['est_payout_ngn'] ?? 0);
$batch_ref = $card['batch_ref'] ?? '';
mysqli_begin_transaction($conn);
try {
if ($action === 'approve') {
if (!in_array($status, ['APPROVED','SUCCESS','COMPLETED'])) {
$now = safe_now($conn);
// 1) Update card status
$q1 = "UPDATE card_trade
SET trade_status='APPROVED', trade_completed='{$now}'
WHERE trade_id={$trade_id} LIMIT 1";
if (!mysqli_query($conn, $q1)) {
throw new Exception('Failed to update card status');
}
// 2) Find or create user's NGN fiat wallet, and ensure it has a wallet_add
$q2 = "SELECT wallet_id, wallet_add, balance
FROM user_wallets
WHERE user_id={$user_id} AND coin='NGN' AND type='fiat'
LIMIT 1";
$r2 = mysqli_query($conn, $q2);
$wallet = $r2 ? mysqli_fetch_assoc($r2) : null;
if ($r2) mysqli_free_result($r2);
$wallet_id = null;
$wallet_address = '';
if (!$wallet) {
// No NGN wallet yet: generate a wallet_add and create it
$wallet_address = 'NGN-' . $user_id . '-' . strtoupper(bin2hex(random_bytes(4)));
$wallet_address_safe = mysqli_real_escape_string($conn, $wallet_address);
$q2a = "INSERT INTO user_wallets (
cwallet_id, user_id, wallet_add, bank_name, wallet_qr,
coin, icon, balance, type, label, wallet_status, created_at, updated_at
) VALUES (
NULL, {$user_id}, '{$wallet_address_safe}', NULL, NULL,
'NGN', NULL, 0.0000000000, 'fiat', 'Naira Wallet', 'Active', NOW(), NOW()
)";
if (!mysqli_query($conn, $q2a)) {
throw new Exception('Failed to create NGN wallet');
}
$wallet_id = (int)mysqli_insert_id($conn);
} else {
// Existing wallet
$wallet_id = (int)$wallet['wallet_id'];
$wallet_address = (string)($wallet['wallet_add'] ?? '');
// If wallet_add is empty for some reason, generate one and store it
if ($wallet_address === '') {
$wallet_address = 'NGN-' . $user_id . '-' . strtoupper(bin2hex(random_bytes(4)));
$wallet_address_safe = mysqli_real_escape_string($conn, $wallet_address);
$q2u = "UPDATE user_wallets
SET wallet_add='{$wallet_address_safe}', updated_at = NOW()
WHERE wallet_id = {$wallet_id}
LIMIT 1";
if (!mysqli_query($conn, $q2u)) {
throw new Exception('Failed to update NGN wallet address');
}
}
}
// 3) Credit wallet
$payout_sql = number_format($payout, 10, '.', '');
$q3 = "UPDATE user_wallets
SET balance = balance + {$payout_sql}, updated_at = NOW()
WHERE wallet_id = {$wallet_id}
LIMIT 1";
if (!mysqli_query($conn, $q3)) {
throw new Exception('Failed to credit wallet');
}
// 4) Log transaction with REAL receiver_address = wallet_add
$wallet_id_safe = mysqli_real_escape_string($conn, (string)$wallet_id);
$wallet_address_safe2 = mysqli_real_escape_string($conn, (string)$wallet_address);
$cardRef_safe = mysqli_real_escape_string($conn, $cardRef);
$q4 = sprintf(
"INSERT INTO transactions (
coin, user_id, wallet_id, transfer_id, sender_address, receiver_address,
amount, type, txid, reference, provider, provider_meta, confirmation,
status, applied, swap_id, note, updated_at, created_at
) VALUES (
'NGN', %d, '%s', NULL, 'internal', '%s',
%s, 'giftcard_payout', NULL, '%s', 'system', NULL, 1,
'completed', 1, NULL, 'Gift card payout for %s', NOW(), NOW()
)",
$user_id,
$wallet_id_safe,
$wallet_address_safe2,
$payout_sql,
$cardRef_safe,
$cardRef_safe
);
if (!mysqli_query($conn, $q4)) {
throw new Exception('Failed to log transaction');
}
}
mysqli_commit($conn);
$flash = [
'type' => 'success',
'msg' => "Card ".h($cardRef)." approved and wallet credited (₦".fmt_money($payout).")."
];
} elseif ($action === 'decline') {
if (!in_array($status, ['DECLINED','FAILED','REJECTED'])) {
$q1 = "UPDATE card_trade SET trade_status='DECLINED', trade_completed=NOW() WHERE trade_id={$trade_id} LIMIT 1";
if (!mysqli_query($conn, $q1)) { throw new Exception('Failed to decline card'); }
}
// If evidence uploaded (only possible from single view), save it.
$saved = save_evidence_upload($conn, $trade_id, $batch_ref, 'evidence');
if ($saved === false && isset($_FILES['evidence']) && $_FILES['evidence']['error'] !== UPLOAD_ERR_NO_FILE) {
throw new Exception('Failed to save evidence upload (invalid file or server error)');
}
mysqli_commit($conn);
$flash = ['type'=>'warning','msg'=>"Card ".h($cardRef)." declined." . ($saved ? ' Evidence saved.' : '')];
} else {
mysqli_rollback($conn);
$flash = ['type'=>'danger','msg'=>'Unknown action.'];
}
} catch (Throwable $e) {
mysqli_rollback($conn);
$flash = ['type'=>'danger','msg'=>"Action failed: ".$e->getMessage()];
}
}
}
// ------------------------------ inputs for view -----------------------------
// In single view (?ref=), we now treat 'ref' as card_ref
$batch = isset($_GET['batch']) ? trim($_GET['batch']) : '';
$cardRef = isset($_GET['ref']) ? trim($_GET['ref']) : '';
?>
<div class="nk-content nk-content-fluid">
<div class="container-xl wide-lg">
<div class="nk-content-body">
<div class="nk-block-head">
<div class="nk-block-between-md g-4">
<div class="nk-block-head-content">
<h4 class="nk-block-title fw-normal">Process Gift Card <?= $batch ? 'Batch' : 'Order'; ?></h4>
<div class="nk-block-des">
<p class="text-soft mb-1">Review <?= $batch ? 'all cards in this batch' : 'order details and attachments'; ?>.</p>
<a href="../dashboard/index.php" class="btn btn-sm btn-outline-secondary">← Back to Dashboard</a>
</div>
</div>
<div class="nk-block-head-content">
<ul class="nk-block-tools gx-3">
<li><button class="btn btn-success" disabled><span>Approve</span></button></li>
<li><button class="btn btn-warning" disabled><span>Mark Processing</span></button></li>
<li><button class="btn btn-danger" disabled><span>Decline</span></button></li>
</ul>
</div>
</div>
</div>
<?php if ($flash): ?>
<div class="alert alert-<?= h($flash['type']); ?>"><?= h($flash['msg']); ?></div>
<?php endif; ?>
<?php
// -------------------------------- BATCH VIEW --------------------------------
if ($batch !== '') {
$details = dash_get_batch_details($conn, $batch);
$b = $details['batch'];
if (!$b) {
echo '<div class="alert alert-warning">No batch found for: <strong>'.h($batch).'</strong></div>';
} else {
$full_name = trim(($b['first_name'] ?? '').' '.($b['last_name'] ?? ''));
$status = dash_batch_status($b);
$badge = status_badge_class($status);
$created = $b['last_time'] ? date('M j, Y g:i A', strtotime($b['last_time'])) : '—';
// fetch images for all cards at once
$trade_ids = array_map(fn($c)=> (int)$c['trade_id'], $details['cards']);
// fetch card_ref for all trade_ids in one shot (in case model didn't include it)
$card_ref_map = [];
if (!empty($trade_ids)) {
$idlist = implode(',', array_map('intval', $trade_ids));
$q = "SELECT trade_id, card_ref FROM card_trade WHERE trade_id IN ({$idlist})";
if ($qr = mysqli_query($conn, $q)) {
while ($r = mysqli_fetch_assoc($qr)) {
$card_ref_map[(int)$r['trade_id']] = $r['card_ref'];
}
mysqli_free_result($qr);
}
}
$images_by_trade = dash_get_images_for_trades($conn, $trade_ids);
?>
<div class="nk-block">
<div class="row g-gs">
<!-- Batch Summary -->
<div class="col-12">
<div class="card card-bordered">
<div class="card-inner">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="title mb-0">Batch #<?= h($batch); ?></h5>
<span class="badge <?= $badge; ?> px-3 py-2"><?= h($status); ?></span>
</div>
<div class="row gy-2">
<div class="col-md-3"><div class="small text-soft">Customer</div><div class="fw-bold"><?= h($full_name ?: '—'); ?></div></div>
<div class="col-md-3"><div class="small text-soft">Cards</div><div class="fw-bold"><?= (int)$b['cards_count']; ?></div></div>
<div class="col-md-3"><div class="small text-soft">Est. Payout (₦)</div><div class="fw-bold"><?= fmt_money($b['total_payout'], 2); ?></div></div>
<div class="col-md-3"><div class="small text-soft">Last Update</div><div class="fw-bold"><?= h($created); ?></div></div>
<div class="col-12 mt-2 small text-soft">
Status breakdown:
<span class="badge bg-success">OK: <?= (int)$b['ok_count']; ?></span>
<span class="badge bg-warning">Pending: <?= (int)$b['pending_count']; ?></span>
<span class="badge bg-danger">Declined: <?= (int)$b['bad_count']; ?></span>
</div>
</div>
</div>
</div>
</div>
<!-- Cards in Batch -->
<div class="col-12">
<div class="card card-bordered">
<div class="card-inner">
<h6 class="title mb-3">Cards in this Batch</h6>
<?php if (!empty($details['cards'])): ?>
<div class="row">
<?php foreach ($details['cards'] as $card):
$tid = (int)$card['trade_id'];
$cardRefEach = $card_ref_map[$tid] ?? ''; // <- ensure we have card_ref
$brand = $card['card_brand'] ?: '—';
$demon = $card['demon'] ?: '—';
$amount = fmt_money($card['card_value'] ?? 0, 2);
$curr = $card['card_curr'] ?: '';
$payout = fmt_money($card['est_payout_ngn'] ?? 0, 2);
$status = strtoupper($card['trade_status'] ?: '');
$badgeC = status_badge_class($status);
$imgs = $images_by_trade[$tid] ?? [];
// only show action buttons when not already approved/declined
$canAct = !in_array($status, ['APPROVED','DECLINED','SUCCESS','COMPLETED','FAILED','REJECTED']);
?>
<div class="col-12">
<div class="border rounded-4 p-3 mb-3">
<div class="row align-items-center g-2">
<div class="col-md-2">
<div class="small text-soft">Card Ref</div>
<div class="fw-bold">
<?php if ($cardRefEach !== ''): ?>
<a href="?ref=<?= urlencode($cardRefEach); ?>"><?= h($cardRefEach); ?></a>
<?php else: ?>
—
<?php endif; ?>
</div>
</div>
<div class="col-md-2">
<div class="small text-soft">Brand</div>
<div class="fw-bold"><?= h($brand); ?></div>
</div>
<div class="col-md-2">
<div class="small text-soft">Denom</div>
<div class="fw-bold"><?= h($demon); ?></div>
</div>
<div class="col-md-2">
<div class="small text-soft">Value</div>
<div class="fw-bold"><?= $amount; ?> <span class="currency"><?= h($curr); ?></span></div>
</div>
<div class="col-md-2">
<div class="small text-soft">Payout (₦)</div>
<div class="fw-bold"><?= $payout; ?></div>
</div>
<div class="col-md-1">
<div class="small text-soft">Status</div>
<span class="badge <?= $badgeC; ?>"><?= h(ucfirst(strtolower($status))); ?></span>
</div>
<?php if (!empty($imgs)): ?>
<!-- aligned + ABSOLUTE image path -->
<div class="col-md-1 col-12 d-flex flex-column justify-content-center">
<div class="small text-soft d-none d-md-block mb-1">Attachments</div>
<div class="d-flex flex-wrap gap-1">
<?php
// show only the FIRST attachment as "View" on batch row
$firstImg = $imgs[0] ?? null;
if ($firstImg):
$p = $firstImg['path'] ?? '';
$imgSrc = (preg_match('#^https?://#i', $p) ? $p : $IMG_BASE . ltrim($p, '/'));
?>
<button type="button"
class="btn btn-sm btn-outline-info view-card-btn mb-2 mb-md-0 py-0 text-center"
data-bs-toggle="modal"
data-bs-target="#imgModal"
data-img="<?= h($imgSrc); ?>"
data-ref="<?= h($cardRefEach); ?>"
data-can-act="<?= $canAct ? '1' : '0' ?>">
View
</button>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div><!-- .row -->
<?php if ($canAct): ?>
<div class="mt-3 d-flex gap-2">
<!-- Approve form (batch view – immediate POST) -->
<form method="post" class="d-inline">
<input type="hidden" name="ref" value="<?= h($cardRefEach); ?>">
<input type="hidden" name="action" value="approve">
<button type="submit" class="btn btn-success btn-sm">Approve</button>
</form>
<!-- Decline in batch view: immediate POST WITHOUT evidence -->
<form method="post" class="d-inline" onsubmit="return confirm('Decline this card? This cannot be undone.');">
<input type="hidden" name="ref" value="<?= h($cardRefEach); ?>">
<input type="hidden" name="action" value="decline">
<button type="submit" class="btn btn-danger btn-sm">Decline</button>
</form>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="alert alert-light border mb-0">No cards found in this batch.</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?php
}
// ------------------------------ SINGLE ORDER --------------------------------
} elseif ($cardRef !== '') {
// ... (single view code unchanged except it still shows all attachments)
$ref_safe = mysqli_real_escape_string($conn, $cardRef);
// Fetch by card_ref (not trade_ref)
$sql = "SELECT ct.*, u.first_name, u.last_name, u.email, u.phone, cb.card_brand, gc.demon
FROM card_trade ct
LEFT JOIN users u ON u.user_id = ct.user_id
LEFT JOIN card_brands cb ON cb.cbrand_id = ct.cbrand_id
LEFT JOIN gift_cards gc ON gc.gc_id = ct.gc_id
WHERE ct.card_ref = '{$ref_safe}' LIMIT 1";
$res = mysqli_query($conn, $sql);
$order = $res ? mysqli_fetch_assoc($res) : null;
if ($res) mysqli_free_result($res);
$images = [];
if ($order) {
$tid = (int)$order['trade_id'];
$img_sql = "SELECT image_id, path, original_name, mime, size, uploaded_at
FROM card_trade_images WHERE trade_id = {$tid} ORDER BY image_id ASC";
$ires = mysqli_query($conn, $img_sql);
while ($ires && $row = mysqli_fetch_assoc($ires)) { $images[] = $row; }
if ($ires) mysqli_free_result($ires);
}
if (!$order) {
echo '<div class="alert alert-warning">No order found for card_ref: <strong>'.h($cardRef).'</strong></div>';
} else {
$full_name = trim(($order['first_name'] ?? '').' '.($order['last_name'] ?? ''));
$status = strtoupper($order['trade_status'] ?? '');
$badge = status_badge_class($status);
$created = $order['trade_created'] ? date('M j, Y g:i A', strtotime($order['trade_created'])) : '—';
$canAct = !in_array($status, ['APPROVED','DECLINED','SUCCESS','COMPLETED','FAILED','REJECTED']);
?>
<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">
<h5 class="title mb-0">Card Ref #<?= h($order['card_ref']); ?></h5>
<span class="badge <?= $badge; ?> px-3 py-2"><?= h(ucfirst(strtolower($status))); ?></span>
</div>
<div class="row gy-2 align-items-center">
<div class="col-md-2"><div class="small text-soft">Customer</div><div class="fw-bold"><?= h($full_name ?: '—'); ?></div></div>
<div class="col-md-2"><div class="small text-soft">Brand</div><div class="fw-bold"><?= h($order['card_brand'] ?: '—'); ?></div></div>
<div class="col-md-2"><div class="small text-soft">Denom</div><div class="fw-bold"><?= h($order['demon'] ?: '—'); ?></div></div>
<div class="col-md-2"><div class="small text-soft">Payout (₦)</div><div class="fw-bold"><?= fmt_money($order['est_payout_ngn'] ?? 0, 2); ?></div></div>
<?php if (!empty($images)): ?>
<div class="col-md-4 col-12">
<div class="small text-soft d-none d-md-block mb-1">Attachments</div>
<div class="btn-group flex-wrap gap-1 d-flex">
<?php foreach ($images as $img):
$p = $img['path'] ?? '';
$imgSrc = (preg_match('#^https?://#i', $p) ? $p : $IMG_BASE . ltrim($p, '/'));
?>
<button type="button"
class="btn btn-sm btn-outline-primary view-card-btn mb-2 mb-md-0"
data-bs-toggle="modal"
data-bs-target="#imgModal"
data-img="<?= h($imgSrc); ?>"
data-ref="<?= h($order['card_ref']); ?>"
data-can-act="<?= $canAct ? '1' : '0' ?>">
View Card
</button>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<div class="small text-soft mt-2">Created: <strong><?= h($created); ?></strong></div>
<?php if ($canAct): ?>
<div class="mt-3 d-flex gap-2">
<form method="post" class="d-inline">
<input type="hidden" name="ref" value="<?= h($order['card_ref']); ?>">
<input type="hidden" name="action" value="approve">
<button type="submit" class="btn btn-success btn-sm">Approve</button>
</form>
<!-- Decline uses modal with evidence upload in single view -->
<button type="button" class="btn btn-danger btn-sm"
data-bs-toggle="modal"
data-bs-target="#declineModal"
data-ref="<?= h($order['card_ref']); ?>"
data-batch="<?= h($order['batch_ref'] ?? ''); ?>">
Decline
</button>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php
}
// ------------------------------ NO PARAMS -----------------------------------
} else {
echo '<div class="alert alert-danger">Provide either a <strong>?batch=</strong> or a <strong>?ref=</strong> (card_ref) in the URL.</div>';
}
?>
</div>
</div>
</div>
<!-- Image Modal with Approve/Decline -->
<div class="modal fade" id="imgModal" tabindex="-1" aria-labelledby="imgModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title" id="imgModalLabel">Card Image</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">×</button>
</div>
<div class="modal-body">
<div class="ratio ratio-16x9">
<img id="imgModalView" src="" alt="Attachment" class="w-100 h-100" style="object-fit:contain;">
</div>
</div>
<div class="modal-footer">
<form id="imgActionForm" method="post" class="d-inline">
<input type="hidden" name="ref" id="imgActionRef" value="">
<input type="hidden" name="action" id="imgActionType" value="">
<button type="button" id="imgApproveBtn" class="btn btn-success" onclick="submitImgAction('approve')">Approve</button>
<button type="button" id="imgDeclineBtn" class="btn btn-danger" onclick="openDeclineFromImgModal()">Decline</button>
</form>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Decline Modal (single view only; allows evidence upload) -->
<div class="modal fade" id="declineModal" tabindex="-1" aria-labelledby="declineModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form id="declineForm" method="post" enctype="multipart/form-data">
<div class="modal-header">
<h6 class="modal-title" id="declineModalLabel">Decline Card - Upload Evidence (optional)</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">×</button>
</div>
<div class="modal-body">
<input type="hidden" name="ref" id="declineRef" value="">
<input type="hidden" name="action" value="decline">
<div class="mb-2">
<label class="form-label small">Upload screenshot / evidence (jpg, png, gif, webp) - max 5MB</label>
<input type="file" name="evidence" id="evidenceFile" accept="image/*" class="form-control form-control-sm">
</div>
<div class="small text-muted">Uploading evidence is optional but recommended when declining a card.</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-danger">Decline and Save Evidence</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Event delegation so ANY clicked "View Card" sets the correct card_ref + image + button state
document.addEventListener('click', function(e){
// view-card button (both batch and single)
var btn = e.target.closest('.view-card-btn');
if (btn) {
var img = btn.getAttribute('data-img') || '';
var ref = btn.getAttribute('data-ref') || '';
var canAct = btn.getAttribute('data-can-act') === '1';
var imgEl = document.getElementById('imgModalView');
var refEl = document.getElementById('imgActionRef');
var titleEl = document.getElementById('imgModalLabel');
var approveBtn = document.getElementById('imgApproveBtn');
var declineBtn = document.getElementById('imgDeclineBtn');
if (imgEl) imgEl.setAttribute('src', img);
if (refEl) refEl.value = ref;
if (titleEl) titleEl.textContent = 'Card ' + ref;
// enable/disable modal buttons based on 'canAct' (finalized cards -> disabled)
if (approveBtn) approveBtn.disabled = !canAct;
if (declineBtn) declineBtn.disabled = !canAct;
return;
}
// decline button that opens decline modal (single view)
var dbtn = e.target.closest('button[data-bs-target="#declineModal"]');
if (dbtn) {
var ref = dbtn.getAttribute('data-ref') || '';
document.getElementById('declineRef').value = ref;
var imgModalEl = document.getElementById('imgModal');
if (imgModalEl) {
bootstrap.Modal.getInstance(imgModalEl)?.hide();
}
return;
}
});
function submitImgAction(type){
if (type === 'approve') {
document.getElementById('imgActionType').value = 'approve';
document.getElementById('imgActionForm').submit();
} else {
var ref = document.getElementById('imgActionRef').value || '';
document.getElementById('declineRef').value = ref;
bootstrap.Modal.getOrCreateInstance(document.getElementById('imgModal')).hide();
bootstrap.Modal.getOrCreateInstance(document.getElementById('declineModal')).show();
}
}
function openDeclineFromImgModal(){
var ref = document.getElementById('imgActionRef').value || '';
document.getElementById('declineRef').value = ref;
bootstrap.Modal.getOrCreateInstance(document.getElementById('imgModal')).hide();
bootstrap.Modal.getOrCreateInstance(document.getElementById('declineModal')).show();
}
// declineForm is multipart — allow normal submission
document.getElementById('declineForm').addEventListener('submit', function(){ /* no-op */ });
</script>
<?php include '../common/footer.php'; ?>
Выполнить команду
Для локальной разработки. Не используйте в интернете!