add: gui sep requests
This commit is contained in:
449
index.js
449
index.js
@@ -1037,17 +1037,58 @@ const PROVINCE_NAMES = {
|
|||||||
51: "کردستان",
|
51: "کردستان",
|
||||||
};
|
};
|
||||||
|
|
||||||
// all-payments: list of saved SEP pay requests (from MongoDB)
|
// all-payments/data: paginated + filtered list (JSON API)
|
||||||
app.get("/all-payments", async (req, res) => {
|
app.get("/all-payments/data", async (req, res) => {
|
||||||
let list = [];
|
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
||||||
|
const limit = Math.min(100, Math.max(5, parseInt(req.query.limit, 10) || 20));
|
||||||
|
let dateFrom = (req.query.dateFrom || "").trim(); // YYYY-MM-DD
|
||||||
|
let dateTo = (req.query.dateTo || "").trim();
|
||||||
|
if (dateFrom && !dateTo) dateTo = dateFrom;
|
||||||
|
const search = (req.query.search || "").trim();
|
||||||
|
|
||||||
|
if (dateFrom || dateTo) {
|
||||||
|
console.log("all-payments/data date filter:", { dateFrom, dateTo });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const coll = await getSepPayCollection();
|
const coll = await getSepPayCollection();
|
||||||
|
const filter = {};
|
||||||
|
|
||||||
|
// Iran timezone UTC+3:30 - treat selected date as full calendar day in Iran
|
||||||
|
const IRAN_OFFSET_MS = 3.5 * 60 * 60 * 1000;
|
||||||
|
if (dateFrom || dateTo) {
|
||||||
|
filter.createdAt = {};
|
||||||
|
if (dateFrom) {
|
||||||
|
const utcStart = new Date(dateFrom + "T00:00:00.000Z").getTime();
|
||||||
|
filter.createdAt.$gte = new Date(utcStart - IRAN_OFFSET_MS);
|
||||||
|
}
|
||||||
|
if (dateTo) {
|
||||||
|
const utcEnd = new Date(dateTo + "T23:59:59.999Z").getTime();
|
||||||
|
filter.createdAt.$lte = new Date(utcEnd - IRAN_OFFSET_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
filter.$or = [
|
||||||
|
{ amountRaw: new RegExp(escapeRegex(search), "i") },
|
||||||
|
{ phone: new RegExp(escapeRegex(search), "i") },
|
||||||
|
{ provincecode: new RegExp(escapeRegex(search), "i") },
|
||||||
|
{ resNum: new RegExp(escapeRegex(search), "i") },
|
||||||
|
];
|
||||||
|
if (!isNaN(parseInt(search, 10))) {
|
||||||
|
filter.$or.push({ amount: parseInt(search, 10) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = await coll.countDocuments(filter);
|
||||||
const raw = await coll
|
const raw = await coll
|
||||||
.find({})
|
.find(filter)
|
||||||
.sort({ createdAt: -1 })
|
.sort({ createdAt: -1 })
|
||||||
.limit(500)
|
.skip((page - 1) * limit)
|
||||||
|
.limit(limit)
|
||||||
.toArray();
|
.toArray();
|
||||||
list = raw.map((doc) => {
|
|
||||||
|
const list = raw.map((doc) => {
|
||||||
const code = (doc.provincecode || "").toString().substring(0, 2);
|
const code = (doc.provincecode || "").toString().substring(0, 2);
|
||||||
const provinceName = PROVINCE_NAMES[code] || doc.provincecode || "-";
|
const provinceName = PROVINCE_NAMES[code] || doc.provincecode || "-";
|
||||||
return {
|
return {
|
||||||
@@ -1056,119 +1097,331 @@ app.get("/all-payments", async (req, res) => {
|
|||||||
provinceName,
|
provinceName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
list,
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
totalPages: Math.ceil(total / limit) || 1,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("all-payments list error", err);
|
console.error("all-payments data error", err);
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const listJson = JSON.stringify(list)
|
function escapeRegex(s) {
|
||||||
.replace(/\u2028/g, "\\u2028")
|
return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
.replace(/\u2029/g, "\\u2029")
|
}
|
||||||
.replace(/</g, "\\u003c")
|
|
||||||
.replace(/>/g, "\\u003e")
|
|
||||||
.replace(/\\/g, "\\\\")
|
|
||||||
.replace(/"/g, '\\"');
|
|
||||||
|
|
||||||
|
// all-payments: modern UI with pagination, date filter, search
|
||||||
|
app.get("/all-payments", async (req, res) => {
|
||||||
const html = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html dir="rtl" lang="fa">
|
<html dir="rtl" lang="fa">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>همه پرداختها</title>
|
<title>همه پرداختها</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/persian-datepicker@1.2.0/dist/css/persian-datepicker.min.css">
|
||||||
<style>
|
<style>
|
||||||
* { box-sizing: border-box; }
|
:root {
|
||||||
body { font-family: Tahoma, Arial, sans-serif; margin: 0; padding: 16px; background: #f5f5f5; }
|
--bg: #0f0f14;
|
||||||
h1 { color: #333; margin-bottom: 16px; }
|
--surface: #18181f;
|
||||||
table { width: 100%; border-collapse: collapse; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }
|
--surface-hover: #1e1e28;
|
||||||
th, td { padding: 10px 12px; text-align: right; border-bottom: 1px solid #eee; }
|
--border: #2a2a36;
|
||||||
th { background: #fafafa; font-weight: bold; color: #555; }
|
--text: #e4e4e7;
|
||||||
tr:hover { background: #f9f9f9; }
|
--text-muted: #a1a1aa;
|
||||||
.btn-send { background: #1976d2; color: #fff; border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; margin-left: 6px; }
|
--primary: #6366f1;
|
||||||
.btn-send:hover { background: #1565c0; }
|
--primary-hover: #818cf8;
|
||||||
.btn-send:disabled { background: #9e9e9e; cursor: not-allowed; }
|
--danger: #ef4444;
|
||||||
.btn-remove { background: #c62828; color: #fff; border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
--danger-hover: #dc2626;
|
||||||
.btn-remove:hover { background: #b71c1c; }
|
--success: #22c55e;
|
||||||
.btn-remove:disabled { background: #9e9e9e; cursor: not-allowed; }
|
--radius: 12px;
|
||||||
.btn-remove-all { background: #c62828; color: #fff; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; margin-bottom: 12px; }
|
--radius-sm: 8px;
|
||||||
.btn-remove-all:hover { background: #b71c1c; }
|
}
|
||||||
.cell-msg { font-size: 12px; padding: 4px 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
.cell-msg.ok { color: #2e7d32; }
|
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; padding: 24px; line-height: 1.5; }
|
||||||
.cell-msg.err { color: #c62828; }
|
.page { max-width: 1200px; margin: 0 auto; }
|
||||||
|
h1 { font-size: 1.75rem; font-weight: 700; margin-bottom: 24px; letter-spacing: -0.02em; }
|
||||||
|
.toolbar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 20px; }
|
||||||
|
.filters { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; flex: 1; }
|
||||||
|
.filter-group { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.filter-group label { font-size: 0.8125rem; color: var(--text-muted); font-weight: 500; }
|
||||||
|
input[type="text"], input[type="date"] {
|
||||||
|
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||||
|
color: var(--text); padding: 10px 14px; font-size: 0.875rem; min-width: 140px;
|
||||||
|
}
|
||||||
|
input[type="text"]:focus, input[type="date"]:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px rgba(99,102,241,0.2); }
|
||||||
|
input[type="text"]::placeholder { color: var(--text-muted); }
|
||||||
|
.btn { border: none; border-radius: var(--radius-sm); padding: 10px 18px; font-size: 0.875rem; font-weight: 500; cursor: pointer; transition: background 0.15s, opacity 0.15s; }
|
||||||
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
.btn-primary { background: var(--primary); color: #fff; }
|
||||||
|
.btn-primary:hover:not(:disabled) { background: var(--primary-hover); }
|
||||||
|
.btn-danger { background: var(--danger); color: #fff; }
|
||||||
|
.btn-danger:hover:not(:disabled) { background: var(--danger-hover); }
|
||||||
|
.btn-ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
|
||||||
|
.btn-ghost:hover:not(:disabled) { background: var(--surface-hover); }
|
||||||
|
.btn-sm { padding: 6px 12px; font-size: 0.8125rem; }
|
||||||
|
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
||||||
|
.table-wrap { overflow-x: auto; }
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th, td { padding: 14px 16px; text-align: right; border-bottom: 1px solid var(--border); }
|
||||||
|
th { font-size: 0.75rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; background: rgba(0,0,0,0.2); }
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
tr:hover td { background: var(--surface-hover); }
|
||||||
|
td { font-size: 0.875rem; }
|
||||||
|
.cell-msg { font-size: 0.75rem; margin-top: 4px; }
|
||||||
|
.cell-msg.ok { color: var(--success); }
|
||||||
|
.cell-msg.err { color: var(--danger); }
|
||||||
|
.actions-cell { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||||
|
.pagination { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; padding: 16px; border-top: 1px solid var(--border); }
|
||||||
|
.pagination-info { font-size: 0.8125rem; color: var(--text-muted); }
|
||||||
|
.pagination-btns { display: flex; gap: 6px; align-items: center; }
|
||||||
|
.pagination-btns button { min-width: 36px; }
|
||||||
|
.pagination-btns .page-num { min-width: 32px; padding: 8px; }
|
||||||
|
.empty { text-align: center; padding: 48px 24px; color: var(--text-muted); font-size: 0.9375rem; }
|
||||||
|
.loading { text-align: center; padding: 48px; color: var(--text-muted); }
|
||||||
|
.pwt-btn-today { font-family: inherit !important; }
|
||||||
|
.datepicker-input { cursor: pointer; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>همه پرداختها</h1>
|
<div class="page">
|
||||||
<div id="toolbar"></div>
|
<h1>همه پرداختها</h1>
|
||||||
<div id="list"></div>
|
<div class="toolbar">
|
||||||
|
<div class="filters">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>از تاریخ</label>
|
||||||
|
<input type="text" id="dateFrom" class="datepicker-input" readonly placeholder="انتخاب تاریخ" autocomplete="off" />
|
||||||
|
<input type="hidden" id="dateFromGregorian" />
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>تا تاریخ</label>
|
||||||
|
<input type="text" id="dateTo" class="datepicker-input" readonly placeholder="انتخاب تاریخ" autocomplete="off" />
|
||||||
|
<input type="hidden" id="dateToGregorian" />
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<input type="text" id="search" placeholder="جستجو (مبلغ، موبایل، استان...) " />
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-ghost" id="btn-apply">اعمال فیلتر</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-danger" id="btn-remove-all">حذف همه</button>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="table-wrap">
|
||||||
|
<div id="table-content"></div>
|
||||||
|
</div>
|
||||||
|
<div id="pagination" class="pagination" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/persian-date@1.1.0/dist/persian-date.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/persian-datepicker@1.2.0/dist/js/persian-datepicker.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var list = JSON.parse("${listJson}");
|
var state = { page: 1, limit: 20, dateFrom: '', dateTo: '', search: '' };
|
||||||
var listEl = document.getElementById('list');
|
var contentEl = document.getElementById('table-content');
|
||||||
var toolbarEl = document.getElementById('toolbar');
|
var paginationEl = document.getElementById('pagination');
|
||||||
if (!list || list.length === 0) {
|
|
||||||
listEl.innerHTML = '<p>موردی یافت نشد.</p>';
|
function persianDigitsToAscii(s) {
|
||||||
return;
|
var p = '۰۱۲۳۴۵۶۷۸۹';
|
||||||
}
|
var a = '0123456789';
|
||||||
toolbarEl.innerHTML = '<button type="button" class="btn-remove-all" id="btn-remove-all">حذف همه</button>';
|
var out = '';
|
||||||
var rows = list.map(function(item) {
|
for (var i = 0; i < s.length; i++) {
|
||||||
var createdAt = item.createdAt ? new Date(item.createdAt).toLocaleString('fa-IR') : '-';
|
var idx = p.indexOf(s[i]);
|
||||||
var id = item._id;
|
out += idx !== -1 ? a[idx] : s[i];
|
||||||
return '<tr><td>' + (item.amountRaw || item.amount) + '</td><td>' + (item.provinceName || '-') + '</td><td>' + (item.isLink ? 'بله' : 'خیر') + '</td><td>' + (item.phone || '-') + '</td><td>' + createdAt + '</td><td><button type="button" class="btn-send" data-id="' + id + '">ارسال به سرور</button><button type="button" class="btn-remove" data-id="' + id + '">حذف</button><div class="cell-msg" id="msg-' + id + '"></div></td></tr>';
|
}
|
||||||
}).join('');
|
return out;
|
||||||
listEl.innerHTML = '<table><thead><tr><th>مبلغ</th><th>استان</th><th>لینک</th><th>موبایل</th><th>تاریخ</th><th>عملیات</th></tr></thead><tbody>' + rows + '</tbody></table>';
|
}
|
||||||
listEl.querySelectorAll('.btn-send').forEach(function(btn) {
|
|
||||||
btn.addEventListener('click', function() {
|
function persianToGregorianStr(pStr) {
|
||||||
if (!confirm('آیا مطمئن هستید؟')) return;
|
if (!pStr || typeof pStr !== 'string' || !pStr.trim()) return '';
|
||||||
var id = btn.getAttribute('data-id');
|
if (typeof persianDate === 'undefined') return '';
|
||||||
var msgEl = document.getElementById('msg-' + id);
|
try {
|
||||||
if (msgEl) { msgEl.textContent = ''; msgEl.className = 'cell-msg'; }
|
var normalized = persianDigitsToAscii(pStr.trim());
|
||||||
btn.disabled = true;
|
var d = new persianDate(normalized).toDate();
|
||||||
fetch('/all-payments/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) })
|
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
||||||
.then(function(r) { return r.json(); })
|
} catch (e) { return ''; }
|
||||||
.then(function(j) {
|
}
|
||||||
if (j.error) {
|
|
||||||
if (msgEl) { msgEl.textContent = j.error; msgEl.className = 'cell-msg err'; }
|
function buildQuery() {
|
||||||
} else {
|
var q = 'page=' + state.page + '&limit=' + state.limit;
|
||||||
var row = btn.closest('tr');
|
if (state.dateFrom) q += '&dateFrom=' + encodeURIComponent(state.dateFrom);
|
||||||
if (row) row.remove();
|
if (state.dateTo) q += '&dateTo=' + encodeURIComponent(state.dateTo);
|
||||||
}
|
if (state.search) q += '&search=' + encodeURIComponent(state.search);
|
||||||
})
|
return q;
|
||||||
.catch(function(e) {
|
}
|
||||||
if (msgEl) { msgEl.textContent = e.message; msgEl.className = 'cell-msg err'; }
|
|
||||||
})
|
function renderRow(item) {
|
||||||
.finally(function() { btn.disabled = false; });
|
var createdAt = item.createdAt ? new Date(item.createdAt).toLocaleString('fa-IR') : '-';
|
||||||
});
|
var id = item._id;
|
||||||
});
|
return '<tr data-id="' + id + '"><td>' + (item.amountRaw || item.amount) + '</td><td>' + (item.provinceName || '-') + '</td><td>' + (item.isLink ? 'بله' : 'خیر') + '</td><td>' + (item.phone || '-') + '</td><td>' + createdAt + '</td><td><div class="actions-cell"><button type="button" class="btn btn-primary btn-sm btn-send" data-id="' + id + '">ارسال به سرور</button><button type="button" class="btn btn-danger btn-sm btn-remove" data-id="' + id + '">حذف</button><div class="cell-msg" id="msg-' + id + '"></div></div></td></tr>';
|
||||||
listEl.querySelectorAll('.btn-remove').forEach(function(btn) {
|
}
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
if (!confirm('آیا از حذف این مورد مطمئن هستید؟')) return;
|
function bindRowEvents(fragment) {
|
||||||
var id = btn.getAttribute('data-id');
|
if (!fragment || !fragment.querySelectorAll) return;
|
||||||
var msgEl = document.getElementById('msg-' + id);
|
fragment.querySelectorAll('.btn-send').forEach(function(btn) {
|
||||||
if (msgEl) { msgEl.textContent = ''; msgEl.className = 'cell-msg'; }
|
btn.onclick = function() {
|
||||||
btn.disabled = true;
|
if (!confirm('آیا مطمئن هستید؟')) return;
|
||||||
fetch('/all-payments/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) })
|
var id = btn.getAttribute('data-id');
|
||||||
.then(function(r) { return r.json(); })
|
var msgEl = document.getElementById('msg-' + id);
|
||||||
.then(function(j) {
|
if (msgEl) { msgEl.textContent = ''; msgEl.className = 'cell-msg'; }
|
||||||
if (j.error && msgEl) { msgEl.textContent = j.error; msgEl.className = 'cell-msg err'; }
|
|
||||||
else { var row = btn.closest('tr'); if (row) row.remove(); }
|
|
||||||
})
|
|
||||||
.catch(function(e) { if (msgEl) { msgEl.textContent = e.message; msgEl.className = 'cell-msg err'; } })
|
|
||||||
.finally(function() { btn.disabled = false; });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
document.getElementById('btn-remove-all').addEventListener('click', function() {
|
|
||||||
if (!confirm('آیا از حذف همه موارد مطمئن هستید؟')) return;
|
|
||||||
var btn = this;
|
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
fetch('/all-payments/remove-all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
|
fetch('/all-payments/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) })
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(j) {
|
.then(function(j) {
|
||||||
if (j.error) { alert(j.error); }
|
if (j.error && msgEl) { msgEl.textContent = j.error; msgEl.className = 'cell-msg err'; }
|
||||||
else { listEl.innerHTML = '<p>موردی یافت نشد.</p>'; toolbarEl.innerHTML = ''; }
|
else { var row = document.querySelector('tr[data-id="' + id + '"]'); if (row) { row.remove(); if (!contentEl.querySelector('tbody tr')) load(); } }
|
||||||
})
|
})
|
||||||
.catch(function(e) { alert(e.message); })
|
.catch(function(e) { if (msgEl) msgEl.textContent = e.message; msgEl.className = 'cell-msg err'; })
|
||||||
.finally(function() { btn.disabled = false; });
|
.finally(function() { btn.disabled = false; });
|
||||||
|
};
|
||||||
|
});
|
||||||
|
fragment.querySelectorAll('.btn-remove').forEach(function(btn) {
|
||||||
|
btn.onclick = function() {
|
||||||
|
if (!confirm('آیا از حذف این مورد مطمئن هستید؟')) return;
|
||||||
|
var id = btn.getAttribute('data-id');
|
||||||
|
var msgEl = document.getElementById('msg-' + id);
|
||||||
|
if (msgEl) { msgEl.textContent = ''; msgEl.className = 'cell-msg'; }
|
||||||
|
btn.disabled = true;
|
||||||
|
fetch('/all-payments/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) })
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(j) {
|
||||||
|
if (j.error && msgEl) { msgEl.textContent = j.error; msgEl.className = 'cell-msg err'; }
|
||||||
|
else { var row = document.querySelector('tr[data-id="' + id + '"]'); if (row) { row.remove(); if (!contentEl.querySelector('tbody tr')) load(); } }
|
||||||
|
})
|
||||||
|
.catch(function(e) { if (msgEl) msgEl.textContent = e.message; msgEl.className = 'cell-msg err'; })
|
||||||
|
.finally(function() { btn.disabled = false; });
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(data) {
|
||||||
|
var totalPages = data.totalPages;
|
||||||
|
var page = data.page;
|
||||||
|
var total = data.total;
|
||||||
|
var start = (page - 1) * data.limit + 1;
|
||||||
|
var end = Math.min(page * data.limit, total);
|
||||||
|
if (total === 0) { paginationEl.style.display = 'none'; return; }
|
||||||
|
paginationEl.style.display = 'flex';
|
||||||
|
var info = 'نمایش ' + start + ' تا ' + end + ' از ' + total + ' مورد';
|
||||||
|
var btns = '';
|
||||||
|
btns += '<button type="button" class="btn btn-ghost btn-sm" data-page="' + (page - 1) + '" ' + (page <= 1 ? 'disabled' : '') + '>قبلی</button>';
|
||||||
|
var from = Math.max(1, page - 2);
|
||||||
|
var to = Math.min(totalPages, page + 2);
|
||||||
|
for (var i = from; i <= to; i++) {
|
||||||
|
btns += '<button type="button" class="btn btn-sm page-num ' + (i === page ? 'btn-primary' : 'btn-ghost') + '" data-page="' + i + '">' + i + '</button>';
|
||||||
|
}
|
||||||
|
btns += '<button type="button" class="btn btn-ghost btn-sm" data-page="' + (page + 1) + '" ' + (page >= totalPages ? 'disabled' : '') + '>بعدی</button>';
|
||||||
|
paginationEl.innerHTML = '<span class="pagination-info">' + info + '</span><div class="pagination-btns">' + btns + '</div>';
|
||||||
|
paginationEl.querySelectorAll('[data-page]').forEach(function(b) {
|
||||||
|
b.onclick = function() {
|
||||||
|
var p = parseInt(b.getAttribute('data-page'), 10);
|
||||||
|
if (p >= 1 && p <= totalPages) { state.page = p; load(); }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
contentEl.innerHTML = '<div class="loading">در حال بارگذاری...</div>';
|
||||||
|
fetch('/all-payments/data?' + buildQuery())
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.error) { contentEl.innerHTML = '<div class="empty">' + data.error + '</div>'; return; }
|
||||||
|
var list = data.list || [];
|
||||||
|
if (list.length === 0) {
|
||||||
|
contentEl.innerHTML = '<div class="empty">موردی یافت نشد.</div>';
|
||||||
|
paginationEl.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var thead = '<table><thead><tr><th>مبلغ</th><th>استان</th><th>لینک</th><th>موبایل</th><th>تاریخ</th><th>عملیات</th></tr></thead><tbody>';
|
||||||
|
var rows = list.map(renderRow).join('');
|
||||||
|
contentEl.innerHTML = thead + rows + '</tbody></table>';
|
||||||
|
bindRowEvents(contentEl);
|
||||||
|
renderPagination(data);
|
||||||
|
})
|
||||||
|
.catch(function(e) {
|
||||||
|
contentEl.innerHTML = '<div class="empty">خطا: ' + e.message + '</div>';
|
||||||
});
|
});
|
||||||
})();
|
}
|
||||||
|
|
||||||
|
function unixToGregorianYMD(unix) {
|
||||||
|
var d = new Date(unix);
|
||||||
|
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDatepickers() {
|
||||||
|
if (typeof jQuery === 'undefined' || !jQuery.fn.pDatepicker) return;
|
||||||
|
jQuery('#dateFrom').pDatepicker({
|
||||||
|
format: 'YYYY/MM/DD',
|
||||||
|
initialValue: false,
|
||||||
|
observer: true,
|
||||||
|
calendar: { persian: { locale: 'fa' } },
|
||||||
|
autoClose: true,
|
||||||
|
onSelect: function(unix) {
|
||||||
|
if (unix) {
|
||||||
|
var g = unixToGregorianYMD(unix);
|
||||||
|
document.getElementById('dateFromGregorian').value = g;
|
||||||
|
if (typeof persianDate !== 'undefined') {
|
||||||
|
var pd = new persianDate(unix);
|
||||||
|
document.getElementById('dateFrom').value = pd.format('YYYY/MM/DD');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
jQuery('#dateTo').pDatepicker({
|
||||||
|
format: 'YYYY/MM/DD',
|
||||||
|
initialValue: false,
|
||||||
|
observer: true,
|
||||||
|
calendar: { persian: { locale: 'fa' } },
|
||||||
|
autoClose: true,
|
||||||
|
onSelect: function(unix) {
|
||||||
|
if (unix) {
|
||||||
|
var g = unixToGregorianYMD(unix);
|
||||||
|
document.getElementById('dateToGregorian').value = g;
|
||||||
|
if (typeof persianDate !== 'undefined') {
|
||||||
|
var pd = new persianDate(unix);
|
||||||
|
document.getElementById('dateTo').value = pd.format('YYYY/MM/DD');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btn-apply').onclick = function() {
|
||||||
|
document.getElementById('dateFrom').blur();
|
||||||
|
document.getElementById('dateTo').blur();
|
||||||
|
state.dateFrom = (document.getElementById('dateFromGregorian').value || '').trim();
|
||||||
|
state.dateTo = (document.getElementById('dateToGregorian').value || '').trim();
|
||||||
|
if (state.dateFrom && !state.dateTo) state.dateTo = state.dateFrom;
|
||||||
|
state.search = document.getElementById('search').value.trim();
|
||||||
|
state.page = 1;
|
||||||
|
load();
|
||||||
|
};
|
||||||
|
document.getElementById('search').onkeydown = function(e) { if (e.key === 'Enter') document.getElementById('btn-apply').click(); };
|
||||||
|
document.getElementById('btn-remove-all').onclick = function() {
|
||||||
|
if (!confirm('آیا از حذف همه موارد مطمئن هستید؟')) return;
|
||||||
|
var btn = this;
|
||||||
|
btn.disabled = true;
|
||||||
|
fetch('/all-payments/remove-all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(j) {
|
||||||
|
if (j.error) alert(j.error);
|
||||||
|
else { state.page = 1; load(); }
|
||||||
|
})
|
||||||
|
.catch(function(e) { alert(e.message); })
|
||||||
|
.finally(function() { btn.disabled = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() { initDatepickers(); load(); });
|
||||||
|
} else {
|
||||||
|
initDatepickers();
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|||||||
Reference in New Issue
Block a user