<?php
/**
 * Billing-sheet upload (coding.bs_upload).  bs-upload-v1
 *
 * A BS-Uploader picks client » process » facility » provider + a billing-sheet DATE and uploads the
 * provider's billing sheet (pdf / word / excel / jpg / png / …). The file is stored under
 * storage/uploads/billing-sheets/ (PHI — excluded from the deploy zip); coders view/download it from
 * My inventory via coding-bs-view.php. Multiple sheets per (tuple + date) are kept (no supersede).
 *
 * The uploader is GLOBAL within Coding (no per-user data scope) — the picker is the whole Coding tree.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');                       // route gate: coding.bs_upload
$pdo  = db();
$uid  = (int) $user['id'];

$BS_ALLOWED_EXT = ['pdf','doc','docx','xls','xlsx','csv','jpg','jpeg','png','gif','tif','tiff'];
$BS_DIR = rtrim(BILLING_SHEETS_DIR, "/\\");                 // persistent PHI store (outside the deploy zip)

/* Permanently delete a billing sheet (its file + record). Logged. Allowed: SysAdmin / a coding manager-supervisor
   (any sheet), or the BS-uploader who uploaded it (their own — to fix a wrong upload). */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') {
    csrf_check_or_die();
    $id  = (int) ($_POST['id'] ?? 0);
    $row = null;
    if ($id) { $g = $pdo->prepare("SELECT * FROM coding_billing_sheets WHERE id=?"); $g->execute([$id]); $row = $g->fetch(PDO::FETCH_ASSOC); }
    if (!$row) { flash('error', 'Billing sheet not found.'); redirect('/admin/coding-bs-upload.php'); }
    if (!is_sysadmin() && !can('coding.supervise') && (int) $row['uploaded_by'] !== $uid) {
        flash('error', 'You can only delete a billing sheet you uploaded (managers / SysAdmin can delete any).');
        redirect('/admin/coding-bs-upload.php');
    }
    $path = $BS_DIR . '/' . basename((string) $row['stored_name']);
    if (is_file($path)) @unlink($path);                     // best-effort; the row goes either way
    $pdo->prepare("DELETE FROM coding_billing_sheets WHERE id=?")->execute([$id]);
    audit_log('coding.bs.delete', 'coding_billing_sheets', $id, $row, null);   // before-snapshot = the delete log
    flash('success', 'Billing sheet deleted.');
    redirect('/admin/coding-bs-upload.php');
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'upload') {
    csrf_check_or_die();
    $cid = (int) input('client_id'); $pid = (int) input('process_id');
    $fid = (int) input('facility_id'); $picked = (int) input('provider_id');
    if (!$cid || !$pid || !$fid || !$picked) { flash('error', 'Pick client, process, facility and provider.'); redirect('/admin/coding-bs-upload.php'); }

    // The posted facility must really belong to the posted process+client (the cascading UI guarantees this, but the
    // server is the trust boundary — a forged tuple would otherwise persist an inconsistent row that the Reconciliation
    // report cannot map back to any coded chart).
    $facOk = $pdo->prepare("SELECT 1 FROM coding_facilities f JOIN processes p ON p.id=f.process_id
        WHERE f.id=? AND f.process_id=? AND p.client_id=? LIMIT 1");
    $facOk->execute([$fid, $pid, $cid]);
    if (!$facOk->fetchColumn()) { flash('error', 'The chosen facility does not belong to that client / process.'); redirect('/admin/coding-bs-upload.php'); }

    // Role (never trust the client): a provider linked as a BILLING provider of THIS client»process»facility → BILLING
    // sheet. Otherwise the picked provider must be a SCRIBE — identified by carrying an NPI (in this data the Type column
    // reads "Billing provider" for everyone, so the reliable signal is the NPI). The dropdown offers exactly these two
    // groups; a provider that is neither a facility billing provider nor an NPI scribe is rejected.
    $prov = $pdo->prepare("SELECT COALESCE(npi,'') FROM coding_providers WHERE id=? AND status='active'");
    $prov->execute([$picked]);
    $pnpi = $prov->fetchColumn();
    if ($pnpi === false) { flash('error', 'The selected provider is not valid (or inactive).'); redirect('/admin/coding-bs-upload.php'); }
    // billing sheet — the picked billing provider must actually bill at THIS client»process»facility
    $isFacBilling = $pdo->prepare("SELECT 1 FROM coding_facility_providers fp
        JOIN coding_facilities f ON f.id=fp.facility_id JOIN processes p ON p.id=f.process_id
        WHERE fp.provider_id=? AND fp.facility_id=? AND f.process_id=? AND p.client_id=? LIMIT 1");
    $isFacBilling->execute([$picked, $fid, $pid, $cid]);
    if ($isFacBilling->fetchColumn()) {
        $prid = $picked; $scribeId = null;
    } elseif (trim((string) $pnpi) !== '') {
        $prid = null; $scribeId = $picked;                         // scribe sheet (has an NPI) — scribe→billing link comes from the coders' charts later
    } else {
        flash('error', 'Pick a billing provider linked to this facility, or a scribe (a provider with an NPI).');
        redirect('/admin/coding-bs-upload.php');
    }

    // Multiple (date + file + patient count) rows for this one provider/scribe. Empty rows skipped; each good row → one sheet.
    $dates  = (array) ($_POST['sheet_date'] ?? []);
    $counts = (array) ($_POST['patient_count'] ?? []);
    $files  = $_FILES['sheet'] ?? null;
    $names  = is_array($files['name'] ?? null) ? $files['name'] : [];
    $count  = max(count($dates), count($names), count($counts));
    if (!is_dir($BS_DIR)) @mkdir($BS_DIR, 0755, true);
    $ins   = $pdo->prepare("INSERT INTO coding_billing_sheets (client_id,process_id,facility_id,provider_id,patient_count,scribe_id,sheet_date,stored_name,original_name,mime,file_size,uploaded_by,created_at)
                            VALUES (?,?,?,?,?,?,?,?,?,?,?,?,NOW())");
    $finfo = function_exists('finfo_open') ? finfo_open(FILEINFO_MIME_TYPE) : null;
    $saved = 0; $errs = [];
    for ($i = 0; $i < $count; $i++) {
        $rawDate  = (string) ($dates[$i] ?? '');
        $rawCount = trim((string) ($counts[$i] ?? ''));
        $err      = $files['error'][$i]    ?? UPLOAD_ERR_NO_FILE;
        $tmp      = (string) ($files['tmp_name'][$i] ?? '');
        $orig     = (string) ($names[$i] ?? '');
        $size     = (int) ($files['size'][$i] ?? 0);
        // A completely empty row (no date AND no file AND no count) is silently skipped — lets the user leave spare rows blank.
        if ($err === UPLOAD_ERR_NO_FILE && trim($rawDate) === '' && $rawCount === '') continue;
        $rowNo = $i + 1;
        $sheetDate = coding_parse_date($rawDate);
        if (!$sheetDate)                                       { $errs[] = "Row $rowNo: pick a valid date."; continue; }
        if ($rawCount === '' || !ctype_digit($rawCount))       { $errs[] = "Row $rowNo: enter the patient count from the sheet (a whole number)."; continue; }
        if (strlen($rawCount) > 6 || (int)$rawCount > 100000)  { $errs[] = "Row $rowNo: patient count looks too large (max 100,000)."; continue; }
        if ($err !== UPLOAD_ERR_OK || !is_uploaded_file($tmp)) { $errs[] = "Row $rowNo: choose a file."; continue; }
        if ($size > UPLOAD_MAX_BYTES)                          { $errs[] = "Row $rowNo (" . $orig . "): too large."; continue; }
        $ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
        if (!in_array($ext, $BS_ALLOWED_EXT, true))            { $errs[] = "Row $rowNo (" . $orig . "): unsupported type."; continue; }
        $mime   = $finfo ? (@finfo_file($finfo, $tmp) ?: null) : ((string) ($files['type'][$i] ?? '') ?: null);
        $stored = date('Ymd_His') . '_' . bin2hex(random_bytes(6)) . '.' . $ext;   // never user-derived → no traversal
        if (!move_uploaded_file($tmp, $BS_DIR . '/' . $stored)) { $errs[] = "Row $rowNo (" . $orig . "): could not save."; continue; }
        $ins->execute([$cid, $pid, $fid, $prid, (int) $rawCount, $scribeId, $sheetDate, $stored, $orig, $mime, $size, $uid]);
        audit_log('coding.bs.upload', 'coding_billing_sheets', (int) $pdo->lastInsertId(), null,
            ['client'=>$cid,'process'=>$pid,'facility'=>$fid,'provider'=>$prid,'scribe'=>$scribeId,'count'=>(int)$rawCount,'date'=>$sheetDate,'file'=>$orig]);
        $saved++;
    }
    if ($finfo) finfo_close($finfo);
    $errMsg = $errs ? (' ' . implode(' ', array_slice($errs, 0, 8)) . (count($errs) > 8 ? ' …' : '')) : '';
    if ($saved && !$errs)  flash('success', "$saved billing sheet(s) uploaded.");
    elseif ($saved)        flash('warning', "$saved uploaded; " . count($errs) . " skipped." . $errMsg);
    elseif ($errs)         flash('error', "Nothing uploaded." . $errMsg);
    else                   flash('error', 'Add at least one date + file.');
    redirect('/admin/coding-bs-upload.php');
}

$tree    = coding_full_facility_tree($pdo);              // global Coding tree to (billing) provider level
$allProviders = coding_all_active_providers($pdo);       // every active provider — JS derives per-facility scribe candidates
$recent = $pdo->query("SELECT bs.*, c.name cn, p.name pn, f.name fn, pr.name prn, sc.name scribe_name, u.name uname
    FROM coding_billing_sheets bs
    JOIN clients c ON c.id=bs.client_id JOIN processes p ON p.id=bs.process_id
    JOIN coding_facilities f ON f.id=bs.facility_id
    LEFT JOIN coding_providers pr ON pr.id=bs.provider_id
    LEFT JOIN coding_providers sc ON sc.id=bs.scribe_id
    LEFT JOIN users u ON u.id=bs.uploaded_by
    ORDER BY bs.created_at DESC LIMIT 50")->fetchAll();

$pageTitle = 'Billing sheets';
require __DIR__ . '/../includes/header.php';
?>
<div class="page-header"><div class="container-xl"><div class="row align-items-center">
    <div class="col"><h2 class="page-title"><i class="ti ti-file-invoice me-2 text-primary"></i>Billing sheets</h2>
        <div class="text-secondary small">Upload a provider's billing sheet for a date. Coders see it on their My inventory page.</div></div>
</div></div></div>
<div class="page-body"><div class="container-xl">
    <div class="card mb-3">
        <div class="card-header"><h3 class="card-title"><i class="ti ti-upload me-1"></i>Upload a billing sheet</h3></div>
        <div class="card-body">
            <?php if (!$tree): ?>
                <div class="text-secondary py-2"><i class="ti ti-alert-triangle me-1"></i>No Coding clients / providers are set up yet.</div>
            <?php else: ?>
            <form method="POST" enctype="multipart/form-data">
                <?= csrf_field() ?><input type="hidden" name="action" value="upload">
                <!-- Pick the provider ONCE; it applies to every row below. -->
                <div class="row g-2 align-items-end">
                    <div class="col-md-3"><label class="form-label required">Client</label>
                        <select name="client_id" id="sel_client" class="form-select" required><option value="">— client —</option></select></div>
                    <div class="col-md-3"><label class="form-label required">Process</label>
                        <select name="process_id" id="sel_process" class="form-select" required><option value="">— process —</option></select></div>
                    <div class="col-md-3"><label class="form-label required">Facility</label>
                        <select name="facility_id" id="sel_facility" class="form-select" required><option value="">— facility —</option></select></div>
                    <div class="col-md-3"><label class="form-label required">Provider <span class="text-secondary small">(type to search)</span></label>
                        <select name="provider_id" id="sel_provider" class="form-select" required><option value="">— provider —</option></select></div>
                </div>
                <!-- hint kept OUTSIDE the row so showing it never shifts the Provider select's alignment -->
                <div class="form-hint mt-1" id="bs_prov_hint"></div>
                <!-- Shown only when the picked provider is a SCRIBE: no billing provider needed — link comes from charts. -->
                <div class="alert alert-purple-lt py-2 mt-1" id="bs_tag_wrap" style="display:none">
                    <i class="ti ti-pencil me-1 text-purple"></i>This name is a <strong>scribe</strong> — no billing provider needed. Just enter the date of service, patient count and file below; the scribe is matched to a billing provider later from the coders' charts (in the Reconciliation report).
                </div>

                <!-- One date + patient count + file per row. Add as many as you need; blank rows are ignored. -->
                <div class="mt-3">
                    <label class="form-label required mb-1">Billing sheets — one row per sheet (date · patient count · file)</label>
                    <div class="row g-2 text-secondary small d-none d-md-flex" style="margin-bottom:2px"><div class="col-md-3">Sheet date</div><div class="col-md-2"># Patients on sheet</div><div class="col-md-5">File</div><div class="col-md-2"></div></div>
                    <div id="bsRows">
                        <?php for ($i = 0; $i < 3; $i++): ?>
                        <div class="row g-2 align-items-end bs-row mb-2">
                            <div class="col-md-3"><input type="date" name="sheet_date[]" class="form-control" aria-label="Billing sheet date"></div>
                            <div class="col-md-2"><input type="number" name="patient_count[]" min="0" step="1" class="form-control" placeholder="# patients" aria-label="Patient count on sheet"></div>
                            <div class="col-md-5"><input type="file" name="sheet[]" class="form-control" aria-label="Billing sheet file"
                                       accept=".pdf,.doc,.docx,.xls,.xlsx,.csv,.jpg,.jpeg,.png,.gif,.tif,.tiff"></div>
                            <div class="col-md-2"><button type="button" class="btn btn-outline-danger w-100 bs-remove" title="Remove this row"><i class="ti ti-trash"></i></button></div>
                        </div>
                        <?php endfor; ?>
                    </div>
                    <button type="button" id="bsAddRow" class="btn btn-sm btn-outline-primary"><i class="ti ti-plus me-1"></i>Add another sheet</button>
                </div>

                <div class="mt-3 d-flex flex-wrap align-items-center gap-2">
                    <button class="btn btn-primary"><i class="ti ti-upload me-1"></i>Upload all</button>
                    <span class="form-hint mb-0">Pick the provider once, then add a row per date &amp; file. Allowed: PDF, Word, Excel, CSV, JPG, PNG, GIF, TIFF — up to <?= (int)(UPLOAD_MAX_BYTES/1048576) ?> MB each.</span>
                </div>
                <?php if (is_sysadmin()):
                    $bsWritable = is_dir($BS_DIR) ? is_writable($BS_DIR) : is_writable(dirname($BS_DIR)); ?>
                    <div class="form-hint mt-2"><i class="ti ti-folder me-1"></i>Files are stored at <code><?= e($BS_DIR) ?></code>
                        — <?= $bsWritable ? '<span class="text-success">writable ✓</span>' : '<span class="text-danger">NOT writable ✗ (uploads will fail / files go missing)</span>' ?>.
                        This folder is <strong>outside the deploy zip</strong>, so replacing the zip does not touch existing sheets.</div>
                <?php endif; ?>
            </form>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.bootstrap5.min.css">
            <script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
            <script>
            (function(){
                var TREE    = <?= json_encode($tree, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_HEX_APOS) ?>;
                // Every active provider (with its master type). Scribe candidates = the dedicated type='scribe'
                // providers only (global — they scribe under any billing provider); billing providers are never scribes.
                var ALLPROV = <?= json_encode(array_map(fn($s)=>['id'=>(int)$s['id'],'name'=>$s['name'],'npi'=>$s['npi'],'type'=>$s['provider_type']], $allProviders), JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_HEX_APOS) ?>;
                var elC=document.getElementById('sel_client'), elP=document.getElementById('sel_process'),
                    elF=document.getElementById('sel_facility'), elPr=document.getElementById('sel_provider'),
                    tagWrap=document.getElementById('bs_tag_wrap'),
                    hint=document.getElementById('bs_prov_hint');
                function opt(v,t,type){ var o=document.createElement('option'); o.value=v; o.textContent=t; if(type) o.dataset.type=type; return o; }
                function nm(x){ return (x && x.name!=null) ? x.name : x; }
                function fillMap(el, map, ph){ el.innerHTML=''; el.appendChild(opt('', ph));
                    Object.keys(map||{}).sort(function(a,b){ return String(nm(map[a])).localeCompare(String(nm(map[b]))); })
                        .forEach(function(k){ el.appendChild(opt(k, nm(map[k]))); }); }
                Object.keys(TREE).sort(function(a,b){ return TREE[a].name.localeCompare(TREE[b].name); }).forEach(function(k){ elC.appendChild(opt(k, TREE[k].name)); });
                // Searchable Provider box — type to filter. Options are rebuilt on every facility change, so we drive
                // them through TomSelect (clearOptions + addOption) and keep the scribe/billing 'type' on each option.
                var providerTS = (typeof TomSelect!=='undefined') ? new TomSelect(elPr, {
                    maxOptions:2000, persist:false, allowEmptyOption:false,
                    placeholder:'Type to search a provider…', sortField:[{field:'$order'}]   // keep billing-then-scribe grouping
                }) : null;
                // facility's BILLING providers (the tree only contains billing providers — scribes are not facility-linked)
                function facBilling(){ var c=TREE[elC.value]; if(!c) return {}; var p=c.processes[elP.value]; if(!p) return {}; var f=p.facilities[elF.value]; return (f&&f.providers)||{}; }
                function provType(){ if(providerTS){ var v=providerTS.getValue(); return (v && providerTS.options[v]) ? (providerTS.options[v].type||'') : ''; }
                    var o=elPr.options[elPr.selectedIndex]; return o?o.dataset.type:''; }
                // Scribe candidates = every active provider that carries an NPI. In this data the Type column reads
                // "Billing provider" for everyone, so the reliable signal is the NPI: a provider WITH an NPI is a scribe,
                // one WITHOUT is a billing provider. Shown at every facility, minus this facility's own billing providers.
                function scribeCandidates(bp){
                    return ALLPROV.filter(function(s){ return String(s.npi||'').trim()!=='' && !Object.prototype.hasOwnProperty.call(bp, String(s.id)); })
                                  .sort(function(a,b){ return a.name.localeCompare(b.name); });
                }
                function fillProviders(){
                    var bp=facBilling();
                    if(providerTS){
                        providerTS.clear(true); providerTS.clearOptions();
                        Object.keys(bp).sort(function(a,b){ return String(nm(bp[a])).localeCompare(String(nm(bp[b]))); })
                            .forEach(function(k){ providerTS.addOption({value:String(k), text:String(nm(bp[k]))+' — Billing', type:'billing'}); });
                        scribeCandidates(bp)
                            .forEach(function(s){ providerTS.addOption({value:String(s.id), text:s.name+' — Scribe'+(s.npi?(' ('+s.npi+')'):''), type:'scribe'}); });
                        providerTS.refreshOptions(false);
                    } else {
                        elPr.innerHTML=''; elPr.appendChild(opt('','— provider —'));
                        Object.keys(bp).sort(function(a,b){ return String(nm(bp[a])).localeCompare(String(nm(bp[b]))); })
                            .forEach(function(k){ elPr.appendChild(opt(k, nm(bp[k])+' — Billing', 'billing')); });
                        scribeCandidates(bp)
                            .forEach(function(s){ elPr.appendChild(opt(s.id, s.name+' — Scribe'+(s.npi?(' ('+s.npi+')'):''), 'scribe')); });
                    }
                    onProvider();
                }
                function onProvider(){
                    var t=provType();
                    if(t==='scribe'){ tagWrap.style.display='';
                        hint.innerHTML='<span class="text-purple"><i class="ti ti-pencil me-1"></i>This is a <strong>scribe</strong> — no billing provider needed.</span>'; }
                    else if(t==='billing'){ tagWrap.style.display='none';
                        hint.innerHTML='<span class="text-blue"><i class="ti ti-user-check me-1"></i>This is the <strong>billing provider</strong> — used as-is.</span>'; }
                    else { tagWrap.style.display='none'; hint.innerHTML=''; }
                }
                function clearProviders(){ if(providerTS){ providerTS.clear(true); providerTS.clearOptions(); providerTS.refreshOptions(false); } else { elPr.innerHTML='<option value="">— provider —</option>'; } }
                function reset(lvl){ if(lvl<=1){ elP.innerHTML='<option value="">— process —</option>'; } if(lvl<=2){ elF.innerHTML='<option value="">— facility —</option>'; } if(lvl<=3){ clearProviders(); tagWrap.style.display='none'; hint.innerHTML=''; } }
                elC.onchange=function(){ reset(1); var c=TREE[elC.value]; if(c){ fillMap(elP, c.processes, '— process —'); } };
                elP.onchange=function(){ reset(2); var c=TREE[elC.value]; if(c && c.processes[elP.value]){ fillMap(elF, c.processes[elP.value].facilities, '— facility —'); } };
                elF.onchange=function(){ reset(3); if(elF.value){ fillProviders(); } };
                if(providerTS){ providerTS.on('change', onProvider); } else { elPr.onchange=onProvider; }

                // ---- repeatable date + count + file rows (multiple sheets for the one selected provider) ----
                var rowsBox=document.getElementById('bsRows'), addBtn=document.getElementById('bsAddRow');
                function wireRemove(btn){ btn.onclick=function(){ var rows=rowsBox.querySelectorAll('.bs-row');
                    if(rows.length>1){ btn.closest('.bs-row').remove(); }
                    else { btn.closest('.bs-row').querySelectorAll('input').forEach(function(i){ i.value=''; }); } }; }
                rowsBox.querySelectorAll('.bs-remove').forEach(wireRemove);
                addBtn.onclick=function(){ var clone=rowsBox.querySelector('.bs-row').cloneNode(true);
                    clone.querySelectorAll('input').forEach(function(i){ i.value=''; });
                    rowsBox.appendChild(clone); wireRemove(clone.querySelector('.bs-remove')); };
            })();
            </script>
            <?php endif; ?>
        </div>
    </div>

    <div class="card">
        <div class="card-header"><h3 class="card-title">Recent uploads</h3><span class="badge bg-blue-lt ms-2"><?= count($recent) ?></span></div>
        <div class="table-responsive"><table class="table table-vcenter card-table">
            <thead><tr><th>Sheet date</th><th>Client</th><th>Facility</th><th>Billing provider</th><th>Scribe</th><th class="text-center"># Patients</th><th>File</th><th>Uploaded by</th><th>When</th><th class="w-1"></th></tr></thead>
            <tbody>
            <?php foreach ($recent as $r): ?>
                <tr>
                    <td class="text-nowrap"><strong><?= e(fmt_date($r['sheet_date'])) ?></strong></td>
                    <td class="small text-secondary"><?= e($r['cn']) ?> » <?= e($r['pn']) ?></td>
                    <td class="small"><?= e($r['fn']) ?></td>
                    <td class="small"><?= $r['prn'] ? e($r['prn']) : '<span class="text-secondary fst-italic">— scribe sheet —</span>' ?></td>
                    <td class="small"><?php if (!empty($r['scribe_name'])): ?><span class="badge bg-purple-lt"><i class="ti ti-pencil me-1"></i><?= e($r['scribe_name']) ?></span><?php else: ?><span class="text-secondary">—</span><?php endif; ?></td>
                    <td class="text-center"><?= $r['patient_count']!==null ? '<span class="badge bg-green-lt">'.(int)$r['patient_count'].'</span>' : '<span class="text-secondary">—</span>' ?></td>
                    <td class="small"><i class="ti ti-file me-1"></i><?= e($r['original_name']) ?></td>
                    <td class="small text-secondary"><?= e($r['uname'] ?? '—') ?></td>
                    <td class="small text-secondary text-nowrap"><?= e(fmt_dt($r['created_at'])) ?></td>
                    <td class="text-nowrap">
                        <a href="<?= e(url('/admin/coding-bs-view.php?id='.(int)$r['id'])) ?>" target="_blank" class="btn btn-sm btn-outline-primary" title="Open"><i class="ti ti-eye"></i></a>
                        <a href="<?= e(url('/admin/coding-bs-view.php?id='.(int)$r['id'].'&dl=1')) ?>" class="btn btn-sm btn-outline-secondary" title="Download"><i class="ti ti-download"></i></a>
                        <?php if (is_sysadmin() || can('coding.supervise') || (int)($r['uploaded_by'] ?? 0) === $uid): ?>
                        <form method="POST" class="d-inline" onsubmit="return confirm('Delete this billing sheet permanently? This removes the file and the record.');">
                            <?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
                            <button class="btn btn-sm btn-outline-danger" title="Delete billing sheet"><i class="ti ti-trash"></i></button>
                        </form>
                        <?php endif; ?>
                    </td>
                </tr>
            <?php endforeach; ?>
            <?php if (!$recent): ?><tr><td colspan="10" class="text-center text-secondary py-4">No billing sheets uploaded yet.</td></tr><?php endif; ?>
            </tbody>
        </table></div>
    </div>
</div></div>
<?php require __DIR__ . '/../includes/footer.php'; ?>
