sync: updated admin, assets, new images, composer config, customer email API

This commit is contained in:
2026-06-21 03:46:45 +00:00
parent 3d9b648f74
commit 145397ab81
17 changed files with 539 additions and 15 deletions
+27
View File
@@ -0,0 +1,27 @@
<?php
/**
* Tom's Java Jive - Admin API: Customer Email History
*/
require_once dirname(__DIR__, 2) . '/includes/auth.php';
AdminAuth::require();
header('Content-Type: application/json');
$customerId = trim($_GET['customer_id'] ?? '');
if (!$customerId) {
echo json_encode(['emails' => []]);
exit;
}
$emails = db()->fetchAll(
"SELECT id, subject, status, opened, open_count, opened_at, tags,
DATE_FORMAT(sent_at, '%b %e, %Y %l:%i %p') as sent_at,
message_id, error_message
FROM email_log
WHERE customer_id = :cid
ORDER BY sent_at DESC
LIMIT 50",
['cid' => $customerId]
);
echo json_encode(['emails' => $emails ?: []]);
+75 -1
View File
@@ -290,6 +290,11 @@ $totalWalletBalance = (float)(db()->fetch("SELECT COALESCE(SUM(wallet_balance),0
<i class="fas fa-shopping-bag"></i> Orders
<span id="cOrderBadge" style="background:var(--color-border);border-radius:10px;padding:1px 7px;font-size:.75rem;margin-left:3px"></span>
</button>
<button type="button" onclick="switchCTab('emails')" id="ctab-emails"
style="padding:.6rem 1rem;border:none;border-bottom:2px solid transparent;background:none;cursor:pointer;font-weight:600;color:var(--color-text-muted);margin-bottom:-1px">
<i class="fas fa-envelope-open-text"></i> Emails
<span id="cEmailBadge" style="background:var(--color-border);border-radius:10px;padding:1px 7px;font-size:.75rem;margin-left:3px"></span>
</button>
</div>
<div id="cpanel-details">
<form method="POST" id="customerForm">
@@ -360,6 +365,37 @@ $totalWalletBalance = (float)(db()->fetch("SELECT COALESCE(SUM(wallet_balance),0
<a id="cOrdViewAll" href="/admin/orders.php" class="btn btn-primary" target="_blank">View All Orders</a>
</div>
</div>
<!-- Emails Panel -->
<div id="cpanel-emails" style="display:none">
<div class="modal-body" style="padding-top:.5rem">
<div id="cEmailLoading" style="text-align:center;padding:2rem;color:var(--color-text-muted)">
<i class="fas fa-spinner fa-spin"></i> Loading emails...
</div>
<div id="cEmailContent" style="display:none">
<div id="cEmailEmpty" style="display:none;text-align:center;padding:2rem;color:var(--color-text-muted)">
<i class="fas fa-envelope" style="font-size:2rem;margin-bottom:.5rem;display:block"></i>
No emails sent to this customer yet.
</div>
<table id="cEmailTable" style="width:100%;border-collapse:collapse;display:none;font-size:.85rem">
<thead>
<tr style="border-bottom:2px solid var(--color-border)">
<th style="padding:.5rem;text-align:left">Subject</th>
<th style="padding:.5rem;text-align:left">Status</th>
<th style="padding:.5rem;text-align:left">Opened</th>
<th style="padding:.5rem;text-align:left">Sent</th>
</tr>
</thead>
<tbody id="cEmailBody"></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<a id="cEmailViewAll" href="#" class="btn btn-primary" target="_blank">
<i class="fas fa-external-link-alt"></i> View All Emails
</a>
</div>
</div>
</div>
</div>
<style>
@@ -372,7 +408,7 @@ $totalWalletBalance = (float)(db()->fetch("SELECT COALESCE(SUM(wallet_balance),0
<script>
// switchCTab defined above
function _switchCTabDummy(tab){
['details','orders'].forEach(function(t){
['details','orders','emails'].forEach(function(t){
document.getElementById('ctab-'+t).style.borderBottomColor=t===tab?'var(--color-primary)':'transparent';
document.getElementById('ctab-'+t).style.color=t===tab?'var(--color-primary)':'var(--color-text-muted)';
document.getElementById('cpanel-'+t).style.display=t===tab?'':'none';
@@ -499,6 +535,44 @@ function openCustomerModal(customer = null) {
const isEdit = !!customer;
_cCustId = isEdit ? customer.customer_id : null;
_cLoaded = false;
// Email history loader
function loadCustomerEmails(customerId){
if(!customerId) return;
document.getElementById('cEmailLoading').style.display='block';
document.getElementById('cEmailContent').style.display='none';
fetch('/admin/api/customer-emails.php?customer_id='+encodeURIComponent(customerId))
.then(r=>r.json()).then(function(data){
document.getElementById('cEmailLoading').style.display='none';
document.getElementById('cEmailContent').style.display='block';
var emails=data.emails||[];
document.getElementById('cEmailBadge').textContent=emails.length;
document.getElementById('cEmailViewAll').href='/admin/email-log.php?customer='+encodeURIComponent(customerId);
if(!emails.length){
document.getElementById('cEmailEmpty').style.display='block';
document.getElementById('cEmailTable').style.display='none';
return;
}
document.getElementById('cEmailEmpty').style.display='none';
document.getElementById('cEmailTable').style.display='table';
var colors={"sent":"#3B82F6","delivered":"#10B981","bounced":"#EF4444","failed":"#EF4444","unknown":"#9CA3AF"};
var html='';
emails.forEach(function(e){
var color=colors[e.status]||'#9CA3AF';
var badge='<span style="background:'+color+';color:white;padding:2px 7px;border-radius:3px;font-size:.75rem;">'+e.status+'</span>';
var opened=e.opened=='1'?'<span style="color:#10B981">✓'+(e.open_count>1?' ('+e.open_count+')':'')+'</span>':'<span style="color:#9CA3AF">—</span>';
html+='<tr style="border-bottom:1px solid var(--color-border)">'
+'<td style="padding:.5rem;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+e.subject+'">'+e.subject+'</td>'
+'<td style="padding:.5rem">'+badge+'</td>'
+'<td style="padding:.5rem">'+opened+'</td>'
+'<td style="padding:.5rem;white-space:nowrap;color:#666">'+e.sent_at+'</td>'
+'</tr>';
});
document.getElementById('cEmailBody').innerHTML=html;
}).catch(function(){
document.getElementById('cEmailLoading').innerHTML='<div style="color:var(--color-error)"><i class="fas fa-exclamation-circle"></i> Failed to load emails</div>';
});
}
document.getElementById('customerModalTitle').textContent = isEdit ? 'Edit Customer' : 'Add Customer';
document.getElementById('customerSubmitBtn').textContent = isEdit ? 'Update Customer' : 'Add Customer';
document.getElementById('customerAction').value = isEdit ? 'update' : 'create';