initial: bootstrap from BukidBountyApp base

This commit is contained in:
Jonathan Sykes
2026-06-06 18:43:00 +08:00
commit eb4a5731fb
5674 changed files with 160857 additions and 0 deletions

View File

@@ -0,0 +1,287 @@
{{-- Admin: Manage Subscription Plans --}}
<div class="card" id="main-card">
<div class="card-header">
<h3 class="card-title">Subscription Plans</h3>
<div class="card-tools">
<button class="btn btn-sm btn-primary" onclick="SubAdmin_showCreateForm()">
<i class="fas fa-plus"></i> New Plan
</button>
</div>
</div>
<div class="card-body">
<div id="sub-admin-plans-loading" class="text-center" style="padding:20px;">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<table class="table table-bordered table-hover" id="sub-admin-plans-table" style="display:none;">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Duration</th>
<th>Expiry Action</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="sub-admin-plans-body"></tbody>
</table>
</div>
</div>
{{-- Create / Edit Plan Form --}}
<div class="card" id="sub-admin-plan-form-card" style="display:none;">
<div class="card-header">
<h3 class="card-title" id="sub-admin-form-title">New Plan</h3>
</div>
<div class="card-body">
<input type="hidden" id="sub-plan-hashkey">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" id="sub-plan-name-field" placeholder="e.g. Monthly Basic">
</div>
<div class="form-group">
<label>Description</label>
<textarea class="form-control" id="sub-plan-desc-field" rows="2" placeholder="Optional description"></textarea>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Price ()</label>
<input type="number" class="form-control" id="sub-plan-price-field" min="0" step="0.01" placeholder="299.00">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Duration (days)</label>
<input type="number" class="form-control" id="sub-plan-duration-field" min="1" placeholder="30">
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>On Expiry</label>
<select class="form-control" id="sub-plan-expiry-field">
<option value="warn">Warn user (show notice)</option>
<option value="restrict">Restrict access</option>
<option value="auto_deduct">Auto-deduct & renew</option>
</select>
</div>
</div>
</div>
<button class="btn btn-success" onclick="SubAdmin_savePlan()">
<i class="fas fa-save"></i> Save Plan
</button>
<button class="btn btn-secondary ml-2" onclick="SubAdmin_cancelForm()">Cancel</button>
</div>
</div>
{{-- All User Subscriptions --}}
<div class="card" id="secondary-card">
<div class="card-header ui-sortable-handle" style="cursor:move;">
<h3 class="card-title">All User Subscriptions</h3>
<div class="card-tools">
<select class="form-control form-control-sm" id="sub-admin-status-filter" onchange="SubAdmin_loadSubscriptions()">
<option value="">All Statuses</option>
<option value="active">Active</option>
<option value="expired">Expired</option>
<option value="cancelled">Cancelled</option>
<option value="pending">Pending</option>
</select>
</div>
</div>
<div class="card-body">
<div id="sub-admin-subs-loading" class="text-center" style="padding:20px; display:none;">
<i class="fas fa-spinner fa-spin"></i> Loading...
</div>
<table class="table table-sm table-striped" id="sub-admin-subs-table" style="display:none;">
<thead>
<tr>
<th>User</th>
<th>Mobile</th>
<th>Plan</th>
<th>Status</th>
<th>Starts</th>
<th>Expires</th>
<th>Method</th>
</tr>
</thead>
<tbody id="sub-admin-subs-body"></tbody>
</table>
<div id="sub-admin-subs-empty" class="text-muted" style="display:none;">No subscriptions found.</div>
</div>
</div>
<script>
(function () {
SubAdmin_loadPlans();
SubAdmin_loadSubscriptions();
// ── Load plans list ──────────────────────────────────────────────────
function SubAdmin_loadPlans() {
$('#sub-admin-plans-loading').show();
$('#sub-admin-plans-table').hide();
$('#sub-admin-plans-body').empty();
$.get('/admin/subscription/plans', function (plans) {
$('#sub-admin-plans-loading').hide();
if (!plans || plans.length === 0) {
$('#sub-admin-plans-body').append('<tr><td colspan="6" class="text-center text-muted">No plans yet.</td></tr>');
$('#sub-admin-plans-table').show();
return;
}
plans.forEach(function (plan) {
const activeBadge = plan.active
? '<span class="badge badge-success">Active</span>'
: '<span class="badge badge-secondary">Inactive</span>';
const expiryLabels = {
'warn': '<span class="badge badge-warning">Warn</span>',
'restrict': '<span class="badge badge-danger">Restrict</span>',
'auto_deduct': '<span class="badge badge-info">Auto-Renew</span>',
};
const row = `<tr>
<td>${escHtml(plan.name)}</td>
<td>₱${parseFloat(plan.price).toFixed(2)}</td>
<td>${parseInt(plan.duration_days)} days</td>
<td>${expiryLabels[plan.expiry_action] || plan.expiry_action}</td>
<td>${activeBadge}</td>
<td>
<button class="btn btn-xs btn-info mr-1" onclick="SubAdmin_editPlan(${JSON.stringify(plan)})">
<i class="fas fa-edit"></i> Edit
</button>
<button class="btn btn-xs ${plan.active ? 'btn-secondary' : 'btn-success'}"
onclick="SubAdmin_togglePlan('${escHtml(plan.hashkey)}', this)">
<i class="fas fa-power-off"></i> ${plan.active ? 'Disable' : 'Enable'}
</button>
</td>
</tr>`;
$('#sub-admin-plans-body').append(row);
});
$('#sub-admin-plans-table').show();
}).fail(function () {
$('#sub-admin-plans-loading').html('<span class="text-danger">Failed to load plans.</span>');
});
}
// ── Create form ──────────────────────────────────────────────────────
window.SubAdmin_showCreateForm = function () {
$('#sub-plan-hashkey').val('');
$('#sub-plan-name-field').val('');
$('#sub-plan-desc-field').val('');
$('#sub-plan-price-field').val('');
$('#sub-plan-duration-field').val('30');
$('#sub-plan-expiry-field').val('warn');
$('#sub-admin-form-title').text('New Plan');
$('#sub-admin-plan-form-card').show();
$('html, body').animate({ scrollTop: $('#sub-admin-plan-form-card').offset().top - 20 }, 300);
};
// ── Edit form ────────────────────────────────────────────────────────
window.SubAdmin_editPlan = function (plan) {
$('#sub-plan-hashkey').val(plan.hashkey);
$('#sub-plan-name-field').val(plan.name);
$('#sub-plan-desc-field').val(plan.description || '');
$('#sub-plan-price-field').val(parseFloat(plan.price).toFixed(2));
$('#sub-plan-duration-field').val(parseInt(plan.duration_days));
$('#sub-plan-expiry-field').val(plan.expiry_action);
$('#sub-admin-form-title').text('Edit Plan: ' + escHtml(plan.name));
$('#sub-admin-plan-form-card').show();
$('html, body').animate({ scrollTop: $('#sub-admin-plan-form-card').offset().top - 20 }, 300);
};
window.SubAdmin_cancelForm = function () {
$('#sub-admin-plan-form-card').hide();
};
// ── Save (create or update) ──────────────────────────────────────────
window.SubAdmin_savePlan = function () {
const hashkey = $('#sub-plan-hashkey').val();
const isEdit = !!hashkey;
const payload = {
name: $('#sub-plan-name-field').val().trim(),
description: $('#sub-plan-desc-field').val().trim(),
price: $('#sub-plan-price-field').val(),
duration_days: $('#sub-plan-duration-field').val(),
expiry_action: $('#sub-plan-expiry-field').val(),
};
if (!payload.name || !payload.price || !payload.duration_days) {
ModalQuickDismiss('Validation', 'Please fill in Name, Price, and Duration.');
return;
}
if (isEdit) payload.hashkey = hashkey;
const url = isEdit ? '/admin/subscription/plan/update' : '/admin/subscription/plan/create';
$.post(url, payload, function (data) {
ModalQuickDismiss('Saved', 'Plan saved successfully.');
$('#sub-admin-plan-form-card').hide();
SubAdmin_loadPlans();
}).fail(function (xhr) {
ModalQuickDismiss('Error', xhr.responseJSON || xhr.responseText || 'Save failed.');
});
};
// ── Toggle active/inactive ───────────────────────────────────────────
window.SubAdmin_togglePlan = function (hashkey, btn) {
$.post('/admin/subscription/plan/toggle', { hashkey: hashkey }, function (data) {
SubAdmin_loadPlans();
}).fail(function (xhr) {
ModalQuickDismiss('Error', xhr.responseJSON || xhr.responseText || 'Toggle failed.');
});
};
// ── Load all user subscriptions ──────────────────────────────────────
window.SubAdmin_loadSubscriptions = function () {
const status = $('#sub-admin-status-filter').val();
$('#sub-admin-subs-loading').show();
$('#sub-admin-subs-table').hide();
$('#sub-admin-subs-empty').hide();
$('#sub-admin-subs-body').empty();
$.post('/admin/subscription/list', { status: status }, function (subs) {
$('#sub-admin-subs-loading').hide();
if (!subs || subs.length === 0) {
$('#sub-admin-subs-empty').show();
return;
}
subs.forEach(function (sub) {
const statusBadges = {
'active': '<span class="badge badge-success">Active</span>',
'expired': '<span class="badge badge-danger">Expired</span>',
'cancelled': '<span class="badge badge-secondary">Cancelled</span>',
'pending': '<span class="badge badge-warning">Pending</span>',
};
const row = `<tr>
<td>${escHtml(sub.user?.name || '—')}</td>
<td>${escHtml(sub.user?.mobile || '—')}</td>
<td>${escHtml(sub.plan?.name || '—')}</td>
<td>${statusBadges[sub.status] || sub.status}</td>
<td>${sub.starts_at ? sub.starts_at.substring(0, 10) : '—'}</td>
<td>${sub.expires_at ? sub.expires_at.substring(0, 10) : '—'}</td>
<td>${escHtml(sub.payment_method)}</td>
</tr>`;
$('#sub-admin-subs-body').append(row);
});
$('#sub-admin-subs-table').show();
}).fail(function () {
$('#sub-admin-subs-loading').html('<span class="text-danger">Failed to load subscriptions.</span>');
});
};
function escHtml(str) {
const d = document.createElement('div');
d.appendChild(document.createTextNode(str ?? ''));
return d.innerHTML;
}
})();
</script>