609 lines
26 KiB
PHP
609 lines
26 KiB
PHP
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||
|
||
/* ═══════════════════════════════════════════
|
||
LAYOUT — Page Shell
|
||
═══════════════════════════════════════════ */
|
||
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||
|
||
.top-bar {
|
||
display: flex;
|
||
justify-content: space-between; /* pushes subtitle left, search right */
|
||
align-items: center; /* vertically centers both */
|
||
padding: 14px 28px;
|
||
background: white;
|
||
/* border-bottom: 1px solid #e0e0e0; */
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
SEARCH INPUT
|
||
═══════════════════════════════════════════ */
|
||
.search-input { padding: 9px 14px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; width: 240px; outline: none; background: #f5f5f5; transition: border-color .2s;}
|
||
.search-input:focus { border-color: #ff6b35; }
|
||
|
||
/* ═══════════════════════════════════════════
|
||
CONTENT AREA
|
||
═══════════════════════════════════════════ */
|
||
.content-area { width: 100%; }
|
||
.content { padding: 10px; width: 100%; max-width: 100%;}
|
||
.subtitle { font-size: 13px; color: #999; margin-bottom: 20px; }
|
||
.view-container { display: none; }
|
||
.view-container.active { display: block; }
|
||
|
||
/* ═══════════════════════════════════════════
|
||
MAIN TABLE
|
||
═══════════════════════════════════════════ */
|
||
.tt-table-wrapper { border: 1px solid #e0e0e0; border-radius: 10px; overflow: hidden; width: 100%;}
|
||
.tt-main-table { width: 100%;border-collapse: collapse;table-layout: fixed;}
|
||
.tt-main-table thead { display: table; width: 100%; table-layout: fixed; }
|
||
.tt-main-table thead th { background: #f4f4f4; padding: 11px 14px; text-align: left;font-size: 12px;font-weight: 700;color: #777;text-transform: uppercase;letter-spacing: .4px;border-bottom: 1px solid #e0e0e0;}
|
||
.tt-main-table tbody { display: block; max-height: 530px; overflow-y: auto;}
|
||
.tt-main-table tbody tr { display: table; width: 100%; table-layout: fixed; }
|
||
.tt-main-table tbody td { padding: 12px 14px; font-size: 14px; border-bottom: 1px solid #f0f0f0; vertical-align: middle;}
|
||
.tt-main-table tbody tr:last-child td { border-bottom: none; }
|
||
.tt-main-table tbody tr:hover td { background: #fafafa; }
|
||
/* Empty row — must stay as table-row (not display:table override) */
|
||
.tt-main-table tbody tr.empty-row { display: table-row; width: 100%; }
|
||
.tt-main-table tbody tr.empty-row td { width: 100%; text-align: center; }
|
||
|
||
/* ═══════════════════════════════════════════
|
||
MEMBER AVATAR (used inside main table)
|
||
═══════════════════════════════════════════ */
|
||
.tt-person-cell { display: flex; align-items: center; gap: 1px;}
|
||
/* .tt-avatar { width: 38px; height: 38px; border-radius: 50%; background: #ff6b35; color: white; font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0;} */
|
||
|
||
/* ═══════════════════════════════════════════
|
||
EMPTY STATE
|
||
═══════════════════════════════════════════ */
|
||
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; color: #aaa; gap: 10px; }
|
||
.empty-icon { font-size: 36px; opacity: 0.5; line-height: 1;}
|
||
.empty-state p { margin: 0; font-size: 14px; font-weight: 500; color: #bbb;}
|
||
.empty-row td { padding: 0 !important; border: none !important; background: transparent !important;}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
MODAL — Overlay & Box
|
||
═══════════════════════════════════════════ */
|
||
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 2000; align-items: center; justify-content: center;}
|
||
.modal.active { display: flex; }
|
||
.modal-box { background: white; border-radius: 14px; width: 92%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, .18); animation: slideUp .2s ease;}
|
||
.modal-header { padding: 6px 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
||
.modal-body { padding: 25px; flex: 1; overflow-y: auto;}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
MODAL — Form Fields
|
||
═══════════════════════════════════════════ */
|
||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px;}
|
||
.form-group { margin-bottom: 20px; }
|
||
.form-label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: #333;}
|
||
|
||
.form-input,
|
||
.form-textarea,
|
||
.form-select {
|
||
width: 100%;
|
||
padding: 10px 15px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
background: #f5f5f5; /* grey background */
|
||
color: #333;
|
||
transition: border-color .2s, background .2s;
|
||
}
|
||
|
||
.form-textarea { resize: vertical; min-height: 100px;}
|
||
|
||
.form-input:focus,
|
||
.form-textarea:focus,
|
||
.form-select:focus { outline: none; border-color: #ff6b35; background: #fff;}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
MODAL — Detail Table (targets list)
|
||
═══════════════════════════════════════════ */
|
||
.table-wrap { border: 1px solid #e0e0e0; border-radius: 10px; overflow: hidden; margin-top: 16px;}
|
||
.table-wrap table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||
.table-wrap thead { display: table; width: 100%; table-layout: fixed; }
|
||
.table-wrap thead th { background: #f8f8f8; padding: 12px 16px; text-align: left; font-size: 13px; font-weight: 600; color: #666; border-bottom: 1px solid #e0e0e0;}
|
||
.table-wrap tbody { display: block; max-height: 220px; overflow-y: auto; }
|
||
.table-wrap tbody tr { display: table; width: 100%; table-layout: fixed; }
|
||
.table-wrap tbody td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; font-size: 14px; color: #333; vertical-align: middle;}
|
||
.table-wrap tbody tr:last-child td { border-bottom: none; }
|
||
.table-wrap tbody tr:hover td { background: #fafafa; }
|
||
/* Empty row fix for modal table */
|
||
/* .table-wrap tbody tr.empty-row { display: table-row; }
|
||
.table-wrap tbody tr.empty-row td { padding: 0 !important; border: none !important; text-align: center;} */
|
||
|
||
/* ═══════════════════════════════════════════
|
||
BUTTONS
|
||
═══════════════════════════════════════════ */
|
||
|
||
/* Primary action — View Target (main table) */
|
||
.btn-tt-view {
|
||
background: #ff6b35;
|
||
color: white;
|
||
border: none;
|
||
padding: 7px 16px;
|
||
border-radius: 7px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background .2s;
|
||
}
|
||
.btn-tt-view:hover { background: #ff5722; }
|
||
|
||
/* Close button — modal header */
|
||
.close-btn {
|
||
background: none;
|
||
border: none;
|
||
font-size: 22px;
|
||
cursor: pointer;
|
||
color: #888;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background .2s;
|
||
}
|
||
.close-btn:hover { background: #f0f0f0; color: #333; }
|
||
|
||
/* Edit row — modal detail table */
|
||
.btn-edit {
|
||
background: #e8f4ff;
|
||
color: #1976d2;
|
||
border: none;
|
||
padding: 5px 12px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
margin-right: 6px;
|
||
transition: background .2s;
|
||
}
|
||
.btn-edit:hover { background: #bbdefb; }
|
||
|
||
/* Remove row — modal detail table */
|
||
.btn-remove {
|
||
background: #ffeaea;
|
||
color: #d32f2f;
|
||
border: none;
|
||
padding: 5px 12px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
transition: background .2s;
|
||
}
|
||
.btn-remove:hover { background: #ffcdd2; }
|
||
|
||
/* ═══════════════════════════════════════════
|
||
ANIMATIONS
|
||
═══════════════════════════════════════════ */
|
||
@keyframes slideUp {
|
||
from { transform: translateY(20px); opacity: 0; }
|
||
to { transform: translateY(0); opacity: 1; }
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
RESPONSIVE
|
||
═══════════════════════════════════════════ */
|
||
@media (max-width: 540px) {
|
||
.form-row { grid-template-columns: 1fr; }
|
||
.search-input { width: 100%; }
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════
|
||
GREY BOX — Input Section Container
|
||
═══════════════════════════════════════════ */
|
||
.grey-box {
|
||
background: #f5f5f5;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 10px;
|
||
padding: 16px 18px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.grey-box .form-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr auto; /* 2 fields + button */
|
||
gap: 15px;
|
||
align-items: flex-end; /* aligns button to bottom of fields */
|
||
}
|
||
|
||
.grey-box .form-group {
|
||
margin-bottom: 0; /* remove default gap inside grey box */
|
||
}
|
||
|
||
.grey-box .btn {
|
||
white-space: nowrap;
|
||
height: 40px; /* match input height */
|
||
width: 100%;
|
||
}
|
||
@media (max-width: 540px) {
|
||
.grey-box .form-row {
|
||
grid-template-columns: 1fr; /* stack vertically on mobile */
|
||
}
|
||
}
|
||
@keyframes fadeHighlight {
|
||
0% { background-color: #fffbcc; }
|
||
100% { background-color: transparent; }
|
||
}
|
||
|
||
.row-highlight {
|
||
animation: fadeHighlight 2s ease forwards;
|
||
}
|
||
|
||
</style>
|
||
<div class="main-content">
|
||
<hr class="my-0">
|
||
<div class="top-bar">
|
||
<p class="subtitle" id="memberCount"></p>
|
||
<input type="text" class="search-input" id="searchInput" placeholder="Search member…" oninput="filterMembers(this.value)">
|
||
</div>
|
||
<!-- Content -->
|
||
<div class="content">
|
||
<div class="content-area view-container active" id="teamtargetView">
|
||
<div class="tt-table-wrapper">
|
||
<table class="tt-main-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Sales Manager</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="ttMainTableBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Modal -->
|
||
<div class="modal" id="targetModal">
|
||
<div class="modal-box">
|
||
<div class="modal-header">
|
||
<span class="modal-title" id="modalTitle">–</span>
|
||
<button class="close-btn" onclick="closeModal()" title="Close">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="recordId">
|
||
<input type="hidden" id="currentUserId">
|
||
|
||
<div class="grey-box">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label class="form-label">Financial Year</label>
|
||
<select class="form-select" id="fyYear"></select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="form-label">Amount (₹)</label>
|
||
<input type="text" class="form-input" id="targetAmount" placeholder="e.g. 500000"
|
||
oninput="this.value=this.value.replace(/[^0-9]/g,'')">
|
||
</div>
|
||
<div class="form-group">
|
||
<button type="button" class="btn btn-primary waves-effect btn-sm w-100" id="btn-save" onclick="saveRecord()">Save Target</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Financial Year</th>
|
||
<th>Amount</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="detailTableBody">
|
||
<tr class="empty-row">
|
||
<td colspan="3">
|
||
<div class="empty-state">
|
||
<div class="empty-icon">🎯</div>
|
||
<p>No targets added yet.</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const sales_manager = <?= json_encode($sales_manager ?? []) ?>;
|
||
|
||
/* ──────────────────────────────────────────
|
||
DATA
|
||
────────────────────────────────────────── */
|
||
const teamMembers = sales_manager.map(user => ({
|
||
id: user.id,
|
||
name: user.first_name
|
||
}));
|
||
const API = '<?= base_url('sales') ?>';
|
||
|
||
let activeUserId = null;
|
||
|
||
/* ──────────────────────────────────────────
|
||
RENDER MEMBER LIST
|
||
────────────────────────────────────────── */
|
||
function renderMemberList(filter = '') {
|
||
const tbody = document.getElementById('ttMainTableBody');
|
||
const countEl = document.getElementById('memberCount');
|
||
const term = filter.trim().toLowerCase();
|
||
|
||
const filtered = teamMembers.filter(m =>
|
||
m.name.toLowerCase().includes(term)
|
||
);
|
||
|
||
|
||
// countEl.textContent = `${filtered.length} member${filtered.length !== 1 ? 's' : ''}`;
|
||
countEl.textContent = ``;
|
||
|
||
tbody.innerHTML = '';
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = `
|
||
<tr class="empty-row">
|
||
<td colspan="2">
|
||
<div class="empty-state">
|
||
<div class="empty-icon">👤</div>
|
||
<p>No Member found</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
filtered.forEach(m => {
|
||
// const initials = m.name.split(' ').map(w => w[0]).join('').substring(0, 2).toUpperCase();
|
||
const tr = document.createElement('tr');
|
||
tr.innerHTML = `
|
||
<td id="card_${m.id}">
|
||
<div class="tt-person-cell">
|
||
<div class="avatar-md">
|
||
<div class="avatar-title bg-soft-primary rounded-circle text-primary font-20 font-weight-semibold"
|
||
style="background-color: #f1f3fa; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;">
|
||
${(m.name || '?').charAt(0).toUpperCase()}
|
||
</div>
|
||
</div>
|
||
<span>${m.name}</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<button class="btn btn-primary waves-effect waves-light" onclick="openModal(${m.id})">View Target</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(tr);
|
||
});
|
||
}
|
||
|
||
function filterMembers(val) {
|
||
renderMemberList(val);
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
FY OPTIONS
|
||
────────────────────────────────────────── */
|
||
function populateFY(selectedFY = null) {
|
||
const sel = document.getElementById('fyYear');
|
||
const today = new Date();
|
||
const year = today.getFullYear();
|
||
const month = today.getMonth(); // 0-11
|
||
|
||
// Determine current FY start year
|
||
let currentFYStart;
|
||
|
||
if (month >= 3) { // April (3) or later
|
||
currentFYStart = year;
|
||
} else { // Jan, Feb, Mar
|
||
currentFYStart = year - 1;
|
||
}
|
||
|
||
sel.innerHTML = '';
|
||
|
||
for (let i = 0; i < 10; i++) {
|
||
const startYear = currentFYStart - i;
|
||
const label = `${startYear}-${startYear + 1}`;
|
||
|
||
const opt = document.createElement('option');
|
||
opt.value = label;
|
||
opt.textContent = label;
|
||
|
||
if (label === selectedFY) opt.selected = true;
|
||
|
||
sel.appendChild(opt);
|
||
}
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
MODAL
|
||
────────────────────────────────────────── */
|
||
function openModal(userId) {
|
||
|
||
activeUserId = userId;
|
||
|
||
const member = teamMembers.find(m => Number(m.id) === Number(userId));
|
||
|
||
document.getElementById('modalTitle').textContent = member ? member.name : '–';
|
||
document.getElementById('currentUserId').value = userId;
|
||
document.getElementById('recordId').value = '';
|
||
document.getElementById('targetAmount').value = '';
|
||
populateFY();
|
||
renderDetailTable(userId);
|
||
document.getElementById('targetModal').classList.add('active');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('targetModal').classList.remove('active');
|
||
activeUserId = null;
|
||
}
|
||
|
||
document.getElementById('targetModal').addEventListener('click', function(e) {
|
||
if (e.target === this) closeModal();
|
||
});
|
||
|
||
/* ──────────────────────────────────────────
|
||
RENDER DETAIL TABLE
|
||
────────────────────────────────────────── */
|
||
async function renderDetailTable(userId) {
|
||
const tbody = document.getElementById('detailTableBody');
|
||
tbody.innerHTML = `<tr><td colspan="3" style="text-align:center; padding:20px;">⏳ Loading targets...</td></tr>`;
|
||
|
||
try {
|
||
const res = await fetch(`${API}/targets/user/${userId}`);
|
||
const json = await res.json();
|
||
const records = json.data || [];
|
||
|
||
if (records.length === 0) {
|
||
tbody.innerHTML = '<tr class="empty-row"><td colspan="3"><div class="empty-state"><div class="empty-icon">🎯</div><p>No targets added yet.</p></div></td></tr>';
|
||
return;
|
||
}
|
||
// records.sort((a, b) => b.id - a.id);
|
||
records.sort((a, b) => {
|
||
let startYearA = parseInt(a.fy_year.split('-')[0], 10);
|
||
let startYearB = parseInt(b.fy_year.split('-')[0], 10);
|
||
return startYearB - startYearA;
|
||
});
|
||
|
||
tbody.innerHTML = records.map(r => `
|
||
<tr id="row_${r.id}">
|
||
<td>${r.fy_year}</td>
|
||
<td>₹ ${formatNum(r.target_amount)}</td>
|
||
<td>
|
||
<button class="btn-edit" onclick="editRecord(${r.id}, '${r.fy_year}', ${r.target_amount})">Edit</button>
|
||
<button class="btn-remove" onclick="removeRecord(${r.id})">Remove</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
} catch (err) {
|
||
tbody.innerHTML = '<tr><td colspan="3">Failed to load targets.</td></tr>';
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
SAVE (INSERT / UPDATE)
|
||
────────────────────────────────────────── */
|
||
async function saveRecord() {
|
||
const userId = activeUserId;
|
||
const recordId = document.getElementById('recordId').value.trim();
|
||
const fyYear = document.getElementById('fyYear').value;
|
||
const amtRaw = document.getElementById('targetAmount').value.trim();
|
||
|
||
// Validation
|
||
if (!fyYear) { toastr.error('Please select a Financial Year.'); return; }
|
||
if (!amtRaw) { toastr.error('Please enter an amount.'); return; }
|
||
const amount = parseInt(amtRaw, 10);
|
||
if (isNaN(amount) || amount <= 0) { toastr.error('Enter a valid positive amount.'); return; }
|
||
|
||
const existingRows = document.querySelectorAll('#detailTableBody tr[id^="row_"]');
|
||
for (const row of existingRows) {
|
||
const rowId = row.id.replace('row_', '');
|
||
const rowFY = row.cells[0].textContent.trim();
|
||
// Skip the row being edited
|
||
if (recordId && rowId === recordId) continue;
|
||
if (rowFY === fyYear) {
|
||
toastr.error(`A target for ${fyYear} already exists.`);
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
const saveBtn = document.getElementById('btn-save');
|
||
const originalBtnText = saveBtn.innerText;
|
||
saveBtn.disabled = true;
|
||
saveBtn.innerText = recordId ? 'Updating...' : 'Saving...';
|
||
|
||
try {
|
||
const payload = {
|
||
user_id: userId,
|
||
fy_year: fyYear,
|
||
target_amount: amount
|
||
};
|
||
|
||
const isUpdate = !!recordId;
|
||
const url = isUpdate ? `${API}/targets/${recordId}` : `${API}/targets`;
|
||
const method = isUpdate ? 'PUT' : 'POST';
|
||
|
||
const res = await fetch(url, {
|
||
method: method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
const result = await res.json();
|
||
|
||
if (res.ok) {
|
||
toastr.success(isUpdate ? 'Target updated!' : 'Target saved!');
|
||
// Reset form
|
||
document.getElementById('recordId').value = '';
|
||
document.getElementById('targetAmount').value = '';
|
||
populateFY();
|
||
await renderDetailTable(userId);
|
||
const savedId = result.data?.id || recordId;
|
||
const targetRow = document.getElementById(`row_${savedId}`);
|
||
if (targetRow) {
|
||
targetRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
targetRow.classList.add('row-highlight');
|
||
setTimeout(() => targetRow.classList.remove('row-highlight'), 3000);
|
||
}
|
||
} else {
|
||
toastr.error(result.message || 'Failed to save target.');
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
toastr.error('A network error occurred.');
|
||
} finally {
|
||
saveBtn.disabled = false;
|
||
saveBtn.innerText = originalBtnText;
|
||
}
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
EDIT — populate form from table row data
|
||
────────────────────────────────────────── */
|
||
function editRecord(id, fyYear, amount) {
|
||
document.getElementById('recordId').value = id;
|
||
document.getElementById('targetAmount').value = amount;
|
||
populateFY(fyYear);
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
REMOVE
|
||
────────────────────────────────────────── */
|
||
async function removeRecord(id) {
|
||
if (!confirm('Are you sure you want to remove this target?')) return;
|
||
|
||
try {
|
||
const res = await fetch(`${API}/targets/${id}`, { method: 'DELETE' });
|
||
const result = await res.json();
|
||
|
||
if (res.ok) {
|
||
toastr.success('Target removed.');
|
||
renderDetailTable(activeUserId);
|
||
} else {
|
||
toastr.error(result.message || 'Failed to remove target.');
|
||
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
toastr.error('A network error occurred.');
|
||
}
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
HELPERS
|
||
────────────────────────────────────────── */
|
||
function formatNum(n) {
|
||
return Number(n).toLocaleString('en-IN');
|
||
}
|
||
|
||
/* ──────────────────────────────────────────
|
||
INIT
|
||
────────────────────────────────────────── */
|
||
renderMemberList();
|
||
</script>
|